Merge branch 'master' into ingame-leaderboard-general-implementation

This commit is contained in:
Dean Herbert
2020-12-15 15:14:56 +09:00
committed by GitHub
2604 changed files with 128254 additions and 36552 deletions

View File

@ -0,0 +1,196 @@
// 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.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Textures;
using osu.Game.Configuration;
using osu.Game.Graphics.Backgrounds;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses;
namespace osu.Game.Tests.Visual.Background
{
public class TestSceneSeasonalBackgroundLoader : ScreenTestScene
{
[Resolved]
private OsuConfigManager config { get; set; }
[Resolved]
private SessionStatics statics { get; set; }
[Cached(typeof(LargeTextureStore))]
private LookupLoggingTextureStore textureStore = new LookupLoggingTextureStore();
private DummyAPIAccess dummyAPI => (DummyAPIAccess)API;
private SeasonalBackgroundLoader backgroundLoader;
private Container backgroundContainer;
// in real usages these would be online URLs, but correct execution of this test
// shouldn't be coupled to existence of online assets.
private static readonly List<string> seasonal_background_urls = new List<string>
{
"Backgrounds/bg2",
"Backgrounds/bg4",
"Backgrounds/bg3"
};
[BackgroundDependencyLoader]
private void load(LargeTextureStore wrappedStore)
{
textureStore.AddStore(wrappedStore);
Add(backgroundContainer = new Container
{
RelativeSizeAxes = Axes.Both
});
}
[SetUp]
public void SetUp() => Schedule(() =>
{
// reset API response in statics to avoid test crosstalk.
statics.Set<APISeasonalBackgrounds>(Static.SeasonalBackgrounds, null);
textureStore.PerformedLookups.Clear();
dummyAPI.SetState(APIState.Online);
backgroundContainer.Clear();
});
[TestCase(-5)]
[TestCase(5)]
public void TestAlwaysSeasonal(int daysOffset)
{
registerBackgroundsResponse(DateTimeOffset.Now.AddDays(daysOffset));
setSeasonalBackgroundMode(SeasonalBackgroundMode.Always);
createLoader();
for (int i = 0; i < 4; ++i)
loadNextBackground();
AddAssert("all backgrounds cycled", () => new HashSet<string>(textureStore.PerformedLookups).SetEquals(seasonal_background_urls));
}
[TestCase(-5)]
[TestCase(5)]
public void TestNeverSeasonal(int daysOffset)
{
registerBackgroundsResponse(DateTimeOffset.Now.AddDays(daysOffset));
setSeasonalBackgroundMode(SeasonalBackgroundMode.Never);
createLoader();
assertNoBackgrounds();
}
[Test]
public void TestSometimesInSeason()
{
registerBackgroundsResponse(DateTimeOffset.Now.AddDays(5));
setSeasonalBackgroundMode(SeasonalBackgroundMode.Sometimes);
createLoader();
assertAnyBackground();
}
[Test]
public void TestSometimesOutOfSeason()
{
registerBackgroundsResponse(DateTimeOffset.Now.AddDays(-10));
setSeasonalBackgroundMode(SeasonalBackgroundMode.Sometimes);
createLoader();
assertNoBackgrounds();
}
[Test]
public void TestDelayedConnectivity()
{
registerBackgroundsResponse(DateTimeOffset.Now.AddDays(30));
setSeasonalBackgroundMode(SeasonalBackgroundMode.Always);
AddStep("go offline", () => dummyAPI.SetState(APIState.Offline));
createLoader();
assertNoBackgrounds();
AddStep("go online", () => dummyAPI.SetState(APIState.Online));
assertAnyBackground();
}
private void registerBackgroundsResponse(DateTimeOffset endDate)
=> AddStep("setup request handler", () =>
{
dummyAPI.HandleRequest = request =>
{
if (dummyAPI.State.Value != APIState.Online || !(request is GetSeasonalBackgroundsRequest backgroundsRequest))
return;
backgroundsRequest.TriggerSuccess(new APISeasonalBackgrounds
{
Backgrounds = seasonal_background_urls.Select(url => new APISeasonalBackground { Url = url }).ToList(),
EndDate = endDate
});
};
});
private void setSeasonalBackgroundMode(SeasonalBackgroundMode mode)
=> AddStep($"set seasonal mode to {mode}", () => config.Set(OsuSetting.SeasonalBackgroundMode, mode));
private void createLoader()
=> AddStep("create loader", () =>
{
if (backgroundLoader != null)
Remove(backgroundLoader);
Add(backgroundLoader = new SeasonalBackgroundLoader());
});
private void loadNextBackground()
{
SeasonalBackground background = null;
AddStep("create next background", () =>
{
background = backgroundLoader.LoadNextBackground();
LoadComponentAsync(background, bg => backgroundContainer.Child = bg);
});
AddUntilStep("background loaded", () => background.IsLoaded);
}
private void assertAnyBackground()
{
loadNextBackground();
AddAssert("background looked up", () => textureStore.PerformedLookups.Any());
}
private void assertNoBackgrounds()
{
AddAssert("no background available", () => backgroundLoader.LoadNextBackground() == null);
AddAssert("no lookups performed", () => !textureStore.PerformedLookups.Any());
}
private class LookupLoggingTextureStore : LargeTextureStore
{
public List<string> PerformedLookups { get; } = new List<string>();
public override Texture Get(string name, WrapMode wrapModeS, WrapMode wrapModeT)
{
PerformedLookups.Add(name);
return base.Get(name, wrapModeS, wrapModeT);
}
}
}
}

View File

@ -0,0 +1,407 @@
// 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 System.Threading;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Events;
using osu.Framework.Input.States;
using osu.Framework.Platform;
using osu.Framework.Screens;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Scoring;
using osu.Game.Screens;
using osu.Game.Screens.Backgrounds;
using osu.Game.Screens.Play;
using osu.Game.Screens.Play.PlayerSettings;
using osu.Game.Screens.Ranking;
using osu.Game.Screens.Select;
using osu.Game.Tests.Beatmaps;
using osu.Game.Tests.Resources;
using osu.Game.Users;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Tests.Visual.Background
{
[TestFixture]
public class TestSceneUserDimBackgrounds : OsuManualInputManagerTestScene
{
private DummySongSelect songSelect;
private TestPlayerLoader playerLoader;
private LoadBlockingTestPlayer player;
private BeatmapManager manager;
private RulesetStore rulesets;
[BackgroundDependencyLoader]
private void load(GameHost host, AudioManager audio)
{
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, host, Beatmap.Default));
Dependencies.Cache(new OsuConfigManager(LocalStorage));
manager.Import(TestResources.GetTestBeatmapForImport()).Wait();
Beatmap.SetDefault();
}
[SetUp]
public virtual void SetUp() => Schedule(() =>
{
var stack = new OsuScreenStack { RelativeSizeAxes = Axes.Both };
Child = stack;
stack.Push(songSelect = new DummySongSelect());
});
/// <summary>
/// Check if <see cref="PlayerLoader"/> properly triggers the visual settings preview when a user hovers over the visual settings panel.
/// </summary>
[Test]
public void TestPlayerLoaderSettingsHover()
{
setupUserSettings();
AddStep("Start player loader", () => songSelect.Push(playerLoader = new TestPlayerLoader(player = new LoadBlockingTestPlayer { BlockLoad = true })));
AddUntilStep("Wait for Player Loader to load", () => playerLoader?.IsLoaded ?? false);
AddAssert("Background retained from song select", () => songSelect.IsBackgroundCurrent());
AddStep("Trigger background preview", () =>
{
InputManager.MoveMouseTo(playerLoader.ScreenPos);
InputManager.MoveMouseTo(playerLoader.VisualSettingsPos);
});
AddUntilStep("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
AddStep("Stop background preview", () => InputManager.MoveMouseTo(playerLoader.ScreenPos));
AddUntilStep("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && playerLoader.IsBlurCorrect());
}
/// <summary>
/// In the case of a user triggering the dim preview the instant player gets loaded, then moving the cursor off of the visual settings:
/// The OnHover of PlayerLoader will trigger, which could potentially cause visual settings to be unapplied unless checked for in PlayerLoader.
/// We need to check that in this scenario, the dim and blur is still properly applied after entering player.
/// </summary>
[Test]
public void TestPlayerLoaderTransition()
{
performFullSetup();
AddStep("Trigger hover event", () => playerLoader.TriggerOnHover());
AddAssert("Background retained from song select", () => songSelect.IsBackgroundCurrent());
AddUntilStep("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
}
/// <summary>
/// Make sure the background is fully invisible (Alpha == 0) when the background should be disabled by the storyboard.
/// </summary>
[Test]
public void TestStoryboardBackgroundVisibility()
{
performFullSetup();
createFakeStoryboard();
AddStep("Enable Storyboard", () =>
{
player.ReplacesBackground.Value = true;
player.StoryboardEnabled.Value = true;
});
AddUntilStep("Background is invisible, storyboard is visible", () => songSelect.IsBackgroundInvisible() && player.IsStoryboardVisible);
AddStep("Disable Storyboard", () =>
{
player.ReplacesBackground.Value = false;
player.StoryboardEnabled.Value = false;
});
AddUntilStep("Background is visible, storyboard is invisible", () => songSelect.IsBackgroundVisible() && !player.IsStoryboardVisible);
}
/// <summary>
/// When exiting player, the screen that it suspends/exits to needs to have a fully visible (Alpha == 1) background.
/// </summary>
[Test]
public void TestStoryboardTransition()
{
performFullSetup();
createFakeStoryboard();
AddStep("Exit to song select", () => player.Exit());
AddUntilStep("Background is visible", () => songSelect.IsBackgroundVisible());
}
/// <summary>
/// Ensure <see cref="UserDimContainer"/> is properly accepting user-defined visual changes for a background.
/// </summary>
[Test]
public void TestDisableUserDimBackground()
{
performFullSetup();
AddUntilStep("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
AddStep("Enable user dim", () => songSelect.DimEnabled.Value = false);
AddUntilStep("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && songSelect.IsUserBlurDisabled());
AddStep("Disable user dim", () => songSelect.DimEnabled.Value = true);
AddUntilStep("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
}
/// <summary>
/// Ensure <see cref="UserDimContainer"/> is properly accepting user-defined visual changes for a storyboard.
/// </summary>
[Test]
public void TestDisableUserDimStoryboard()
{
performFullSetup();
createFakeStoryboard();
AddStep("Enable Storyboard", () =>
{
player.ReplacesBackground.Value = true;
player.StoryboardEnabled.Value = true;
});
AddStep("Enable user dim", () => player.DimmableStoryboard.EnableUserDim.Value = true);
AddStep("Set dim level to 1", () => songSelect.DimLevel.Value = 1f);
AddUntilStep("Storyboard is invisible", () => !player.IsStoryboardVisible);
AddStep("Disable user dim", () => player.DimmableStoryboard.EnableUserDim.Value = false);
AddUntilStep("Storyboard is visible", () => player.IsStoryboardVisible);
}
/// <summary>
/// Check if the visual settings container retains dim and blur when pausing
/// </summary>
[Test]
public void TestPause()
{
performFullSetup(true);
AddStep("Pause", () => player.Pause());
AddUntilStep("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
AddStep("Unpause", () => player.Resume());
AddUntilStep("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
}
/// <summary>
/// Check if the visual settings container removes user dim when suspending <see cref="Player"/> for <see cref="ResultsScreen"/>
/// </summary>
[Test]
public void TestTransition()
{
performFullSetup();
FadeAccessibleResults results = null;
AddStep("Transition to Results", () => player.Push(results = new FadeAccessibleResults(new ScoreInfo
{
User = new User { Username = "osu!" },
Beatmap = new TestBeatmap(Ruleset.Value).BeatmapInfo,
Ruleset = Ruleset.Value,
})));
AddUntilStep("Wait for results is current", () => results.IsCurrentScreen());
AddUntilStep("Screen is undimmed, original background retained", () =>
songSelect.IsBackgroundUndimmed() && songSelect.IsBackgroundCurrent() && results.IsBlurCorrect());
}
/// <summary>
/// Check if background gets undimmed and unblurred when leaving <see cref="Player"/> for <see cref="PlaySongSelect"/>
/// </summary>
[Test]
public void TestTransitionOut()
{
performFullSetup();
AddStep("Exit to song select", () => player.Exit());
AddUntilStep("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && songSelect.IsBlurCorrect());
}
/// <summary>
/// Check if hovering on the visual settings dialogue after resuming from player still previews the background dim.
/// </summary>
[Test]
public void TestResumeFromPlayer()
{
performFullSetup();
AddStep("Move mouse to Visual Settings", () => InputManager.MoveMouseTo(playerLoader.VisualSettingsPos));
AddStep("Resume PlayerLoader", () => player.Restart());
AddUntilStep("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
AddStep("Move mouse to center of screen", () => InputManager.MoveMouseTo(playerLoader.ScreenPos));
AddUntilStep("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && playerLoader.IsBlurCorrect());
}
private void createFakeStoryboard() => AddStep("Create storyboard", () =>
{
player.StoryboardEnabled.Value = false;
player.ReplacesBackground.Value = false;
player.DimmableStoryboard.Add(new OsuSpriteText
{
Size = new Vector2(500, 50),
Alpha = 1,
Colour = Color4.White,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Text = "THIS IS A STORYBOARD",
Font = new FontUsage(size: 50)
});
});
private void performFullSetup(bool allowPause = false)
{
setupUserSettings();
AddStep("Start player loader", () => songSelect.Push(playerLoader = new TestPlayerLoader(player = new LoadBlockingTestPlayer(allowPause))));
AddUntilStep("Wait for Player Loader to load", () => playerLoader.IsLoaded);
AddStep("Move mouse to center of screen", () => InputManager.MoveMouseTo(playerLoader.ScreenPos));
AddUntilStep("Wait for player to load", () => player.IsLoaded);
}
private void setupUserSettings()
{
AddUntilStep("Song select is current", () => songSelect.IsCurrentScreen());
AddUntilStep("Song select has selection", () => songSelect.Carousel?.SelectedBeatmap != null);
AddStep("Set default user settings", () =>
{
SelectedMods.Value = SelectedMods.Value.Concat(new[] { new OsuModNoFail() }).ToArray();
songSelect.DimLevel.Value = 0.7f;
songSelect.BlurLevel.Value = 0.4f;
});
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
rulesets?.Dispose();
}
private class DummySongSelect : PlaySongSelect
{
protected override BackgroundScreen CreateBackground()
{
FadeAccessibleBackground background = new FadeAccessibleBackground(Beatmap.Value);
DimEnabled.BindTo(background.EnableUserDim);
return background;
}
public readonly Bindable<bool> DimEnabled = new Bindable<bool>();
public readonly Bindable<double> DimLevel = new BindableDouble();
public readonly Bindable<double> BlurLevel = new BindableDouble();
public new BeatmapCarousel Carousel => base.Carousel;
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
{
config.BindWith(OsuSetting.DimLevel, DimLevel);
config.BindWith(OsuSetting.BlurLevel, BlurLevel);
}
public bool IsBackgroundDimmed() => ((FadeAccessibleBackground)Background).CurrentColour == OsuColour.Gray(1f - ((FadeAccessibleBackground)Background).CurrentDim);
public bool IsBackgroundUndimmed() => ((FadeAccessibleBackground)Background).CurrentColour == Color4.White;
public bool IsUserBlurApplied() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2((float)BlurLevel.Value * BackgroundScreenBeatmap.USER_BLUR_FACTOR);
public bool IsUserBlurDisabled() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2(0);
public bool IsBackgroundInvisible() => ((FadeAccessibleBackground)Background).CurrentAlpha == 0;
public bool IsBackgroundVisible() => ((FadeAccessibleBackground)Background).CurrentAlpha == 1;
public bool IsBlurCorrect() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2(BACKGROUND_BLUR);
/// <summary>
/// Make sure every time a screen gets pushed, the background doesn't get replaced
/// </summary>
/// <returns>Whether or not the original background (The one created in DummySongSelect) is still the current background</returns>
public bool IsBackgroundCurrent() => ((FadeAccessibleBackground)Background).IsCurrentScreen();
}
private class FadeAccessibleResults : ResultsScreen
{
public FadeAccessibleResults(ScoreInfo score)
: base(score, true)
{
}
protected override BackgroundScreen CreateBackground() => new FadeAccessibleBackground(Beatmap.Value);
public bool IsBlurCorrect() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2(BACKGROUND_BLUR);
}
private class LoadBlockingTestPlayer : TestPlayer
{
protected override BackgroundScreen CreateBackground() => new FadeAccessibleBackground(Beatmap.Value);
public new DimmableStoryboard DimmableStoryboard => base.DimmableStoryboard;
// Whether or not the player should be allowed to load.
public bool BlockLoad;
public Bindable<bool> StoryboardEnabled;
public readonly Bindable<bool> ReplacesBackground = new Bindable<bool>();
public readonly Bindable<bool> IsPaused = new Bindable<bool>();
public LoadBlockingTestPlayer(bool allowPause = true)
: base(allowPause)
{
}
public bool IsStoryboardVisible => DimmableStoryboard.ContentDisplayed;
[BackgroundDependencyLoader]
private void load(OsuConfigManager config, CancellationToken token)
{
while (BlockLoad && !token.IsCancellationRequested)
Thread.Sleep(1);
StoryboardEnabled = config.GetBindable<bool>(OsuSetting.ShowStoryboard);
ReplacesBackground.BindTo(Background.StoryboardReplacesBackground);
DrawableRuleset.IsPaused.BindTo(IsPaused);
}
}
private class TestPlayerLoader : PlayerLoader
{
public VisualSettings VisualSettingsPos => VisualSettings;
public BackgroundScreen ScreenPos => Background;
public TestPlayerLoader(Player player)
: base(() => player)
{
}
public void TriggerOnHover() => OnHover(new HoverEvent(new InputState()));
public bool IsBlurCorrect() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2(BACKGROUND_BLUR);
protected override BackgroundScreen CreateBackground() => new FadeAccessibleBackground(Beatmap.Value);
}
private class FadeAccessibleBackground : BackgroundScreenBeatmap
{
protected override DimmableBackground CreateFadeContainer() => dimmable = new TestDimmableBackground { RelativeSizeAxes = Axes.Both };
public Color4 CurrentColour => dimmable.CurrentColour;
public float CurrentAlpha => dimmable.CurrentAlpha;
public float CurrentDim => dimmable.DimLevel;
public Vector2 CurrentBlur => Background.BlurSigma;
private TestDimmableBackground dimmable;
public FadeAccessibleBackground(WorkingBeatmap beatmap)
: base(beatmap)
{
}
}
private class TestDimmableBackground : BackgroundScreenBeatmap.DimmableBackground
{
public Color4 CurrentColour => Content.Colour;
public float CurrentAlpha => Content.Alpha;
public new float DimLevel => base.DimLevel;
}
}
}

View File

@ -1,416 +1,112 @@
// 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 System.Threading;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Events;
using osu.Framework.Input.States;
using osu.Framework.Platform;
using osu.Framework.Screens;
using osu.Game.Beatmaps;
using osu.Framework.Graphics.Shapes;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Scoring;
using osu.Game.Screens;
using osu.Game.Screens.Backgrounds;
using osu.Game.Screens.Play;
using osu.Game.Screens.Play.PlayerSettings;
using osu.Game.Screens.Select;
using osu.Game.Tests.Resources;
using osu.Game.Users;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Tests.Visual.Background
{
[TestFixture]
public class TestSceneUserDimContainer : ManualInputManagerTestScene
public class TestSceneUserDimContainer : OsuTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(ScreenWithBeatmapBackground),
typeof(PlayerLoader),
typeof(Player),
typeof(UserDimContainer),
typeof(OsuScreen)
};
private TestUserDimContainer userDimContainer;
private DummySongSelect songSelect;
private TestPlayerLoader playerLoader;
private TestPlayer player;
private BeatmapManager manager;
private RulesetStore rulesets;
private readonly BindableBool isBreakTime = new BindableBool();
private Bindable<bool> lightenDuringBreaks = new Bindable<bool>();
[BackgroundDependencyLoader]
private void load(GameHost host, AudioManager audio)
private void load(OsuConfigManager config)
{
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, host, Beatmap.Default));
Dependencies.Cache(new OsuConfigManager(LocalStorage));
manager.Import(TestResources.GetTestBeatmapForImport()).Wait();
Beatmap.SetDefault();
lightenDuringBreaks = config.GetBindable<bool>(OsuSetting.LightenDuringBreaks);
}
[SetUp]
public virtual void SetUp() => Schedule(() =>
public void SetUp() => Schedule(() =>
{
Child = new OsuScreenStack(songSelect = new DummySongSelect())
Child = userDimContainer = new TestUserDimContainer
{
RelativeSizeAxes = Axes.Both
RelativeSizeAxes = Axes.Both,
Child = new Box
{
Colour = Color4.White,
RelativeSizeAxes = Axes.Both,
},
};
userDimContainer.IsBreakTime.BindTo(isBreakTime);
isBreakTime.Value = false;
lightenDuringBreaks.Value = false;
});
/// <summary>
/// Check if <see cref="PlayerLoader"/> properly triggers the visual settings preview when a user hovers over the visual settings panel.
/// </summary>
private const float test_user_dim = 0.6f;
private const float test_user_dim_lightened = test_user_dim - UserDimContainer.BREAK_LIGHTEN_AMOUNT;
[TestCase(test_user_dim, test_user_dim_lightened)]
[TestCase(0.2f, 0.0f)]
[TestCase(0.0f, 0.0f)]
public void TestBreakLightening(float userDim, float expectedBreakDim)
{
AddStep($"set dim level {userDim}", () => userDimContainer.UserDimLevel.Value = userDim);
AddStep("set lighten during break", () => lightenDuringBreaks.Value = true);
AddStep("set break", () => isBreakTime.Value = true);
AddUntilStep("has lightened", () => userDimContainer.DimEqual(expectedBreakDim));
AddStep("clear break", () => isBreakTime.Value = false);
AddUntilStep("not lightened", () => userDimContainer.DimEqual(userDim));
}
[Test]
public void PlayerLoaderSettingsHoverTest()
public void TestEnableSettingDuringBreak()
{
setupUserSettings();
AddStep("Start player loader", () => songSelect.Push(playerLoader = new TestPlayerLoader(player = new TestPlayer { BlockLoad = true })));
AddUntilStep("Wait for Player Loader to load", () => playerLoader?.IsLoaded ?? false);
AddAssert("Background retained from song select", () => songSelect.IsBackgroundCurrent());
AddStep("Trigger background preview", () =>
{
InputManager.MoveMouseTo(playerLoader.ScreenPos);
InputManager.MoveMouseTo(playerLoader.VisualSettingsPos);
});
waitForDim();
AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
AddStep("Stop background preview", () => InputManager.MoveMouseTo(playerLoader.ScreenPos));
waitForDim();
AddAssert("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && playerLoader.IsBlurCorrect());
AddStep("set dim level 0.6", () => userDimContainer.UserDimLevel.Value = test_user_dim);
AddStep("set break", () => isBreakTime.Value = true);
AddUntilStep("not lightened", () => userDimContainer.DimEqual(test_user_dim));
AddStep("set lighten during break", () => lightenDuringBreaks.Value = true);
AddUntilStep("has lightened", () => userDimContainer.DimEqual(test_user_dim_lightened));
}
/// <summary>
/// In the case of a user triggering the dim preview the instant player gets loaded, then moving the cursor off of the visual settings:
/// The OnHover of PlayerLoader will trigger, which could potentially cause visual settings to be unapplied unless checked for in PlayerLoader.
/// We need to check that in this scenario, the dim and blur is still properly applied after entering player.
/// </summary>
[Test]
public void PlayerLoaderTransitionTest()
public void TestDisableSettingDuringBreak()
{
performFullSetup();
AddStep("Trigger hover event", () => playerLoader.TriggerOnHover());
AddAssert("Background retained from song select", () => songSelect.IsBackgroundCurrent());
waitForDim();
AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
AddStep("set dim level 0.6", () => userDimContainer.UserDimLevel.Value = test_user_dim);
AddStep("set lighten during break", () => lightenDuringBreaks.Value = true);
AddStep("set break", () => isBreakTime.Value = true);
AddUntilStep("has lightened", () => userDimContainer.DimEqual(test_user_dim_lightened));
AddStep("clear lighten during break", () => lightenDuringBreaks.Value = false);
AddUntilStep("not lightened", () => userDimContainer.DimEqual(test_user_dim));
AddStep("clear break", () => isBreakTime.Value = false);
AddUntilStep("not lightened", () => userDimContainer.DimEqual(test_user_dim));
}
/// <summary>
/// Make sure the background is fully invisible (Alpha == 0) when the background should be disabled by the storyboard.
/// </summary>
[Test]
public void StoryboardBackgroundVisibilityTest()
public void TestIgnoreUserSettings()
{
performFullSetup();
createFakeStoryboard();
AddStep("Enable Storyboard", () =>
{
player.ReplacesBackground.Value = true;
player.StoryboardEnabled.Value = true;
});
waitForDim();
AddAssert("Background is invisible, storyboard is visible", () => songSelect.IsBackgroundInvisible() && player.IsStoryboardVisible);
AddStep("Disable Storyboard", () =>
{
player.ReplacesBackground.Value = false;
player.StoryboardEnabled.Value = false;
});
waitForDim();
AddAssert("Background is visible, storyboard is invisible", () => songSelect.IsBackgroundVisible() && !player.IsStoryboardVisible);
AddStep("set dim level 0.6", () => userDimContainer.UserDimLevel.Value = test_user_dim);
AddUntilStep("dim reached", () => userDimContainer.DimEqual(test_user_dim));
AddStep("ignore settings", () => userDimContainer.IgnoreUserSettings.Value = true);
AddUntilStep("no dim", () => userDimContainer.DimEqual(0));
AddStep("set break", () => isBreakTime.Value = true);
AddAssert("no dim", () => userDimContainer.DimEqual(0));
AddStep("clear break", () => isBreakTime.Value = false);
AddAssert("no dim", () => userDimContainer.DimEqual(0));
}
/// <summary>
/// When exiting player, the screen that it suspends/exits to needs to have a fully visible (Alpha == 1) background.
/// </summary>
[Test]
public void StoryboardTransitionTest()
private class TestUserDimContainer : UserDimContainer
{
performFullSetup();
createFakeStoryboard();
AddStep("Exit to song select", () => player.Exit());
waitForDim();
AddAssert("Background is visible", () => songSelect.IsBackgroundVisible());
}
public bool DimEqual(float expectedDimLevel) => Content.Colour == OsuColour.Gray(1f - expectedDimLevel);
/// <summary>
/// Ensure <see cref="UserDimContainer"/> is properly accepting user-defined visual changes for a background.
/// </summary>
[Test]
public void DisableUserDimBackgroundTest()
{
performFullSetup();
waitForDim();
AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
AddStep("Enable user dim", () => songSelect.DimEnabled.Value = false);
waitForDim();
AddAssert("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && songSelect.IsUserBlurDisabled());
AddStep("Disable user dim", () => songSelect.DimEnabled.Value = true);
waitForDim();
AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
}
/// <summary>
/// Ensure <see cref="UserDimContainer"/> is properly accepting user-defined visual changes for a storyboard.
/// </summary>
[Test]
public void DisableUserDimStoryboardTest()
{
performFullSetup();
createFakeStoryboard();
AddStep("Enable Storyboard", () =>
{
player.ReplacesBackground.Value = true;
player.StoryboardEnabled.Value = true;
});
AddStep("Enable user dim", () => player.DimmableStoryboard.EnableUserDim.Value = true);
AddStep("Set dim level to 1", () => songSelect.DimLevel.Value = 1f);
waitForDim();
AddAssert("Storyboard is invisible", () => !player.IsStoryboardVisible);
AddStep("Disable user dim", () => player.DimmableStoryboard.EnableUserDim.Value = false);
waitForDim();
AddAssert("Storyboard is visible", () => player.IsStoryboardVisible);
}
/// <summary>
/// Check if the visual settings container retains dim and blur when pausing
/// </summary>
[Test]
public void PauseTest()
{
performFullSetup(true);
AddStep("Pause", () => player.Pause());
waitForDim();
AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
AddStep("Unpause", () => player.Resume());
waitForDim();
AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
}
/// <summary>
/// Check if the visual settings container removes user dim when suspending <see cref="Player"/> for <see cref="SoloResults"/>
/// </summary>
[Test]
public void TransitionTest()
{
performFullSetup();
var results = new FadeAccessibleResults(new ScoreInfo { User = new User { Username = "osu!" } });
AddStep("Transition to Results", () => player.Push(results));
AddUntilStep("Wait for results is current", results.IsCurrentScreen);
waitForDim();
AddAssert("Screen is undimmed, original background retained", () =>
songSelect.IsBackgroundUndimmed() && songSelect.IsBackgroundCurrent() && results.IsBlurCorrect());
}
/// <summary>
/// Check if background gets undimmed and unblurred when leaving <see cref="Player"/> for <see cref="PlaySongSelect"/>
/// </summary>
[Test]
public void TransitionOutTest()
{
performFullSetup();
AddStep("Exit to song select", () => player.Exit());
waitForDim();
AddAssert("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && songSelect.IsBlurCorrect());
}
/// <summary>
/// Check if hovering on the visual settings dialogue after resuming from player still previews the background dim.
/// </summary>
[Test]
public void ResumeFromPlayerTest()
{
performFullSetup();
AddStep("Move mouse to Visual Settings", () => InputManager.MoveMouseTo(playerLoader.VisualSettingsPos));
AddStep("Resume PlayerLoader", () => player.Restart());
waitForDim();
AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
AddStep("Move mouse to center of screen", () => InputManager.MoveMouseTo(playerLoader.ScreenPos));
waitForDim();
AddAssert("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && playerLoader.IsBlurCorrect());
}
private void waitForDim() => AddWaitStep("Wait for dim", 5);
private void createFakeStoryboard() => AddStep("Create storyboard", () =>
{
player.StoryboardEnabled.Value = false;
player.ReplacesBackground.Value = false;
player.DimmableStoryboard.Add(new OsuSpriteText
{
Size = new Vector2(500, 50),
Alpha = 1,
Colour = Color4.White,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Text = "THIS IS A STORYBOARD",
Font = new FontUsage(size: 50)
});
});
private void performFullSetup(bool allowPause = false)
{
setupUserSettings();
AddStep("Start player loader", () => songSelect.Push(playerLoader = new TestPlayerLoader(player = new TestPlayer(allowPause))));
AddUntilStep("Wait for Player Loader to load", () => playerLoader.IsLoaded);
AddStep("Move mouse to center of screen", () => InputManager.MoveMouseTo(playerLoader.ScreenPos));
AddUntilStep("Wait for player to load", () => player.IsLoaded);
}
private void setupUserSettings()
{
AddUntilStep("Song select has selection", () => songSelect.Carousel.SelectedBeatmap != null);
AddStep("Set default user settings", () =>
{
Mods.Value = Mods.Value.Concat(new[] { new OsuModNoFail() }).ToArray();
songSelect.DimLevel.Value = 0.7f;
songSelect.BlurLevel.Value = 0.4f;
});
}
private class DummySongSelect : PlaySongSelect
{
protected override BackgroundScreen CreateBackground()
{
FadeAccessibleBackground background = new FadeAccessibleBackground(Beatmap.Value);
DimEnabled.BindTo(background.EnableUserDim);
return background;
}
public readonly Bindable<bool> DimEnabled = new Bindable<bool>();
public readonly Bindable<double> DimLevel = new Bindable<double>();
public readonly Bindable<double> BlurLevel = new Bindable<double>();
public new BeatmapCarousel Carousel => base.Carousel;
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
{
config.BindWith(OsuSetting.DimLevel, DimLevel);
config.BindWith(OsuSetting.BlurLevel, BlurLevel);
}
public bool IsBackgroundDimmed() => ((FadeAccessibleBackground)Background).CurrentColour == OsuColour.Gray(1 - (float)DimLevel.Value);
public bool IsBackgroundUndimmed() => ((FadeAccessibleBackground)Background).CurrentColour == Color4.White;
public bool IsUserBlurApplied() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2((float)BlurLevel.Value * BackgroundScreenBeatmap.USER_BLUR_FACTOR);
public bool IsUserBlurDisabled() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2(0);
public bool IsBackgroundInvisible() => ((FadeAccessibleBackground)Background).CurrentAlpha == 0;
public bool IsBackgroundVisible() => ((FadeAccessibleBackground)Background).CurrentAlpha == 1;
public bool IsBlurCorrect() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2(BACKGROUND_BLUR);
/// <summary>
/// Make sure every time a screen gets pushed, the background doesn't get replaced
/// </summary>
/// <returns>Whether or not the original background (The one created in DummySongSelect) is still the current background</returns>
public bool IsBackgroundCurrent() => ((FadeAccessibleBackground)Background).IsCurrentScreen();
}
private class FadeAccessibleResults : SoloResults
{
public FadeAccessibleResults(ScoreInfo score)
: base(score)
{
}
protected override BackgroundScreen CreateBackground() => new FadeAccessibleBackground(Beatmap.Value);
public bool IsBlurCorrect() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2(BACKGROUND_BLUR);
}
private class TestPlayer : Visual.TestPlayer
{
protected override BackgroundScreen CreateBackground() => new FadeAccessibleBackground(Beatmap.Value);
public new DimmableStoryboard DimmableStoryboard => base.DimmableStoryboard;
// Whether or not the player should be allowed to load.
public bool BlockLoad;
public Bindable<bool> StoryboardEnabled;
public readonly Bindable<bool> ReplacesBackground = new Bindable<bool>();
public readonly Bindable<bool> IsPaused = new Bindable<bool>();
public TestPlayer(bool allowPause = true)
: base(allowPause)
{
}
public bool IsStoryboardVisible => DimmableStoryboard.ContentDisplayed;
[BackgroundDependencyLoader]
private void load(OsuConfigManager config, CancellationToken token)
{
while (BlockLoad && !token.IsCancellationRequested)
Thread.Sleep(1);
StoryboardEnabled = config.GetBindable<bool>(OsuSetting.ShowStoryboard);
ReplacesBackground.BindTo(Background.StoryboardReplacesBackground);
DrawableRuleset.IsPaused.BindTo(IsPaused);
}
}
private class TestPlayerLoader : PlayerLoader
{
public VisualSettings VisualSettingsPos => VisualSettings;
public BackgroundScreen ScreenPos => Background;
public TestPlayerLoader(Player player)
: base(() => player)
{
}
public void TriggerOnHover() => OnHover(new HoverEvent(new InputState()));
public bool IsBlurCorrect() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2(BACKGROUND_BLUR);
protected override BackgroundScreen CreateBackground() => new FadeAccessibleBackground(Beatmap.Value);
}
private class FadeAccessibleBackground : BackgroundScreenBeatmap
{
protected override DimmableBackground CreateFadeContainer() => dimmable = new TestDimmableBackground { RelativeSizeAxes = Axes.Both };
public Color4 CurrentColour => dimmable.CurrentColour;
public float CurrentAlpha => dimmable.CurrentAlpha;
public Vector2 CurrentBlur => Background.BlurSigma;
private TestDimmableBackground dimmable;
public FadeAccessibleBackground(WorkingBeatmap beatmap)
: base(beatmap)
{
}
}
private class TestDimmableBackground : BackgroundScreenBeatmap.DimmableBackground
{
public Color4 CurrentColour => Content.Colour;
public float CurrentAlpha => Content.Alpha;
public new Bindable<double> UserDimLevel => base.UserDimLevel;
}
}
}

View File

@ -0,0 +1,235 @@
// 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.UserInterface;
using osu.Framework.Platform;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Collections;
using osu.Game.Overlays;
using osu.Game.Overlays.Dialog;
using osu.Game.Rulesets;
using osu.Game.Tests.Resources;
using osuTK;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Collections
{
public class TestSceneManageCollectionsDialog : OsuManualInputManagerTestScene
{
protected override Container<Drawable> Content { get; } = new Container { RelativeSizeAxes = Axes.Both };
private DialogOverlay dialogOverlay;
private CollectionManager manager;
private RulesetStore rulesets;
private BeatmapManager beatmapManager;
private ManageCollectionsDialog dialog;
[BackgroundDependencyLoader]
private void load(GameHost host)
{
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, Audio, host, Beatmap.Default));
beatmapManager.Import(TestResources.GetTestBeatmapForImport()).Wait();
base.Content.AddRange(new Drawable[]
{
manager = new CollectionManager(LocalStorage),
Content,
dialogOverlay = new DialogOverlay()
});
Dependencies.Cache(manager);
Dependencies.Cache(dialogOverlay);
}
[SetUp]
public void SetUp() => Schedule(() =>
{
manager.Collections.Clear();
Child = dialog = new ManageCollectionsDialog();
});
[SetUpSteps]
public void SetUpSteps()
{
AddStep("show dialog", () => dialog.Show());
}
[Test]
public void TestHideDialog()
{
AddWaitStep("wait for animation", 3);
AddStep("hide dialog", () => dialog.Hide());
}
[Test]
public void TestLastItemIsPlaceholder()
{
AddAssert("last item is placeholder", () => !manager.Collections.Contains(dialog.ChildrenOfType<DrawableCollectionListItem>().Last().Model));
}
[Test]
public void TestAddCollectionExternal()
{
AddStep("add collection", () => manager.Collections.Add(new BeatmapCollection { Name = { Value = "First collection" } }));
assertCollectionCount(1);
assertCollectionName(0, "First collection");
AddStep("add another collection", () => manager.Collections.Add(new BeatmapCollection { Name = { Value = "Second collection" } }));
assertCollectionCount(2);
assertCollectionName(1, "Second collection");
}
[Test]
public void TestFocusPlaceholderDoesNotCreateCollection()
{
AddStep("focus placeholder", () =>
{
InputManager.MoveMouseTo(dialog.ChildrenOfType<DrawableCollectionListItem>().Last());
InputManager.Click(MouseButton.Left);
});
assertCollectionCount(0);
}
[Test]
public void TestAddCollectionViaPlaceholder()
{
DrawableCollectionListItem placeholderItem = null;
AddStep("focus placeholder", () =>
{
InputManager.MoveMouseTo(placeholderItem = dialog.ChildrenOfType<DrawableCollectionListItem>().Last());
InputManager.Click(MouseButton.Left);
});
// Done directly via the collection since InputManager methods cannot add text to textbox...
AddStep("change collection name", () => placeholderItem.Model.Name.Value = "a");
assertCollectionCount(1);
AddAssert("collection now exists", () => manager.Collections.Contains(placeholderItem.Model));
AddAssert("last item is placeholder", () => !manager.Collections.Contains(dialog.ChildrenOfType<DrawableCollectionListItem>().Last().Model));
}
[Test]
public void TestRemoveCollectionExternal()
{
AddStep("add two collections", () => manager.Collections.AddRange(new[]
{
new BeatmapCollection { Name = { Value = "1" } },
new BeatmapCollection { Name = { Value = "2" } },
}));
AddStep("remove first collection", () => manager.Collections.RemoveAt(0));
assertCollectionCount(1);
assertCollectionName(0, "2");
}
[Test]
public void TestRemoveCollectionViaButton()
{
AddStep("add two collections", () => manager.Collections.AddRange(new[]
{
new BeatmapCollection { Name = { Value = "1" } },
new BeatmapCollection { Name = { Value = "2" }, Beatmaps = { beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0] } },
}));
assertCollectionCount(2);
AddStep("click first delete button", () =>
{
InputManager.MoveMouseTo(dialog.ChildrenOfType<DrawableCollectionListItem.DeleteButton>().First(), new Vector2(5, 0));
InputManager.Click(MouseButton.Left);
});
AddAssert("dialog not displayed", () => dialogOverlay.CurrentDialog == null);
assertCollectionCount(1);
assertCollectionName(0, "2");
AddStep("click first delete button", () =>
{
InputManager.MoveMouseTo(dialog.ChildrenOfType<DrawableCollectionListItem.DeleteButton>().First(), new Vector2(5, 0));
InputManager.Click(MouseButton.Left);
});
AddAssert("dialog displayed", () => dialogOverlay.CurrentDialog is DeleteCollectionDialog);
AddStep("click confirmation", () =>
{
InputManager.MoveMouseTo(dialogOverlay.CurrentDialog.ChildrenOfType<PopupDialogButton>().First());
InputManager.Click(MouseButton.Left);
});
assertCollectionCount(0);
}
[Test]
public void TestCollectionNotRemovedWhenDialogCancelled()
{
AddStep("add two collections", () => manager.Collections.AddRange(new[]
{
new BeatmapCollection { Name = { Value = "1" }, Beatmaps = { beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0] } },
}));
assertCollectionCount(1);
AddStep("click first delete button", () =>
{
InputManager.MoveMouseTo(dialog.ChildrenOfType<DrawableCollectionListItem.DeleteButton>().First(), new Vector2(5, 0));
InputManager.Click(MouseButton.Left);
});
AddAssert("dialog displayed", () => dialogOverlay.CurrentDialog is DeleteCollectionDialog);
AddStep("click cancellation", () =>
{
InputManager.MoveMouseTo(dialogOverlay.CurrentDialog.ChildrenOfType<PopupDialogButton>().Last());
InputManager.Click(MouseButton.Left);
});
assertCollectionCount(1);
}
[Test]
public void TestCollectionRenamedExternal()
{
AddStep("add two collections", () => manager.Collections.AddRange(new[]
{
new BeatmapCollection { Name = { Value = "1" } },
new BeatmapCollection { Name = { Value = "2" } },
}));
AddStep("change first collection name", () => manager.Collections[0].Name.Value = "First");
assertCollectionName(0, "First");
}
[Test]
public void TestCollectionRenamedOnTextChange()
{
AddStep("add two collections", () => manager.Collections.AddRange(new[]
{
new BeatmapCollection { Name = { Value = "1" } },
new BeatmapCollection { Name = { Value = "2" } },
}));
assertCollectionCount(2);
AddStep("change first collection name", () => dialog.ChildrenOfType<TextBox>().First().Text = "First");
AddAssert("collection has new name", () => manager.Collections[0].Name.Value == "First");
}
private void assertCollectionCount(int count)
=> AddUntilStep($"{count} collections shown", () => dialog.ChildrenOfType<DrawableCollectionListItem>().Count(i => i.IsCreated.Value) == count);
private void assertCollectionName(int index, string name)
=> AddUntilStep($"item {index + 1} has correct name", () => dialog.ChildrenOfType<DrawableCollectionListItem>().ElementAt(index).ChildrenOfType<TextBox>().First().Text == name);
}
}

View File

@ -12,7 +12,7 @@ using osuTK.Graphics;
namespace osu.Game.Tests.Visual.Components
{
[TestFixture]
public class TestSceneIdleTracker : ManualInputManagerTestScene
public class TestSceneIdleTracker : OsuManualInputManagerTestScene
{
private IdleTrackingBox box1;
private IdleTrackingBox box2;

View File

@ -3,16 +3,20 @@
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Track;
using osu.Framework.Graphics.Containers;
using osu.Game.Audio;
using osu.Game.Beatmaps;
using static osu.Game.Tests.Visual.Components.TestScenePreviewTrackManager.TestPreviewTrackManager;
namespace osu.Game.Tests.Visual.Components
{
public class TestScenePreviewTrackManager : OsuTestScene, IPreviewTrackOwner
{
private readonly PreviewTrackManager trackManager = new TestPreviewTrackManager();
private readonly TestPreviewTrackManager trackManager = new TestPreviewTrackManager();
private AudioManager audio;
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
{
@ -23,8 +27,10 @@ namespace osu.Game.Tests.Visual.Components
}
[BackgroundDependencyLoader]
private void load()
private void load(AudioManager audio)
{
this.audio = audio;
Add(trackManager);
}
@ -34,6 +40,7 @@ namespace osu.Game.Tests.Visual.Components
PreviewTrack track = null;
AddStep("get track", () => track = getOwnedTrack());
AddUntilStep("wait loaded", () => track.IsLoaded);
AddStep("start", () => track.Start());
AddAssert("started", () => track.IsRunning);
AddStep("stop", () => track.Stop());
@ -52,10 +59,15 @@ namespace osu.Game.Tests.Visual.Components
track2 = getOwnedTrack();
});
AddUntilStep("wait loaded", () => track1.IsLoaded && track2.IsLoaded);
AddStep("start track 1", () => track1.Start());
AddStep("start track 2", () => track2.Start());
AddAssert("track 1 stopped", () => !track1.IsRunning);
AddAssert("track 2 started", () => track2.IsRunning);
AddStep("start track 1", () => track1.Start());
AddAssert("track 2 stopped", () => !track2.IsRunning);
AddAssert("track 1 started", () => track1.IsRunning);
}
[Test]
@ -64,6 +76,7 @@ namespace osu.Game.Tests.Visual.Components
PreviewTrack track = null;
AddStep("get track", () => track = getOwnedTrack());
AddUntilStep("wait loaded", () => track.IsLoaded);
AddStep("start", () => track.Start());
AddStep("stop by owner", () => trackManager.StopAnyPlaying(this));
AddAssert("stopped", () => !track.IsRunning);
@ -76,6 +89,7 @@ namespace osu.Game.Tests.Visual.Components
PreviewTrack track = null;
AddStep("get track", () => Add(owner = new TestTrackOwner(track = getTrack())));
AddUntilStep("wait loaded", () => track.IsLoaded);
AddStep("start", () => track.Start());
AddStep("attempt stop", () => trackManager.StopAnyPlaying(this));
AddAssert("not stopped", () => track.IsRunning);
@ -83,40 +97,150 @@ namespace osu.Game.Tests.Visual.Components
AddAssert("stopped", () => !track.IsRunning);
}
private PreviewTrack getTrack() => trackManager.Get(null);
[Test]
public void TestNonPresentTrack()
{
TestPreviewTrack track = null;
private PreviewTrack getOwnedTrack()
AddStep("get non-present track", () =>
{
Add(new TestTrackOwner(track = getTrack()));
track.Alpha = 0;
});
AddUntilStep("wait loaded", () => track.IsLoaded);
AddStep("start", () => track.Start());
AddStep("seek to end", () => track.Track.Seek(track.Track.Length));
AddAssert("track stopped", () => !track.IsRunning);
}
/// <summary>
/// Ensures that <see cref="PreviewTrackManager.CurrentTrack"/> changes correctly.
/// </summary>
[Test]
public void TestCurrentTrackChanges()
{
PreviewTrack track = null;
TestTrackOwner owner = null;
AddStep("get track", () => Add(owner = new TestTrackOwner(track = getTrack())));
AddUntilStep("wait loaded", () => track.IsLoaded);
AddStep("start track", () => track.Start());
AddAssert("current is track", () => trackManager.CurrentTrack == track);
AddStep("pause manager updates", () => trackManager.AllowUpdate = false);
AddStep("stop any playing", () => trackManager.StopAnyPlaying(owner));
AddAssert("current not changed", () => trackManager.CurrentTrack == track);
AddStep("resume manager updates", () => trackManager.AllowUpdate = true);
AddAssert("current is null", () => trackManager.CurrentTrack == null);
}
/// <summary>
/// Ensures that <see cref="PreviewTrackManager"/> mutes game-wide audio tracks correctly.
/// </summary>
[TestCase(false)]
[TestCase(true)]
public void TestEnsureMutingCorrectly(bool stopAnyPlaying)
{
PreviewTrack track = null;
TestTrackOwner owner = null;
AddStep("ensure volume not zero", () =>
{
if (audio.Volume.Value == 0)
audio.Volume.Value = 1;
if (audio.VolumeTrack.Value == 0)
audio.VolumeTrack.Value = 1;
});
AddAssert("game not muted", () => audio.Tracks.AggregateVolume.Value != 0);
AddStep("get track", () => Add(owner = new TestTrackOwner(track = getTrack())));
AddUntilStep("wait loaded", () => track.IsLoaded);
AddStep("start track", () => track.Start());
AddAssert("game is muted", () => audio.Tracks.AggregateVolume.Value == 0);
if (stopAnyPlaying)
AddStep("stop any playing", () => trackManager.StopAnyPlaying(owner));
else
AddStep("stop track", () => track.Stop());
AddAssert("game not muted", () => audio.Tracks.AggregateVolume.Value != 0);
}
[Test]
public void TestOwnerNotRegistered()
{
PreviewTrack track = null;
AddStep("get track", () => Add(new TestTrackOwner(track = getTrack(), registerAsOwner: false)));
AddUntilStep("wait for loaded", () => track.IsLoaded);
AddStep("start track", () => track.Start());
AddUntilStep("track is running", () => track.IsRunning);
AddStep("cancel from anyone", () => trackManager.StopAnyPlaying(this));
AddAssert("track stopped", () => !track.IsRunning);
}
private TestPreviewTrack getTrack() => (TestPreviewTrack)trackManager.Get(null);
private TestPreviewTrack getOwnedTrack()
{
var track = getTrack();
Add(track);
LoadComponentAsync(track, Add);
return track;
}
private class TestTrackOwner : CompositeDrawable, IPreviewTrackOwner
{
public TestTrackOwner(PreviewTrack track)
private readonly PreviewTrack track;
private readonly bool registerAsOwner;
public TestTrackOwner(PreviewTrack track, bool registerAsOwner = true)
{
AddInternal(track);
this.track = track;
this.registerAsOwner = registerAsOwner;
}
[BackgroundDependencyLoader]
private void load()
{
LoadComponentAsync(track, AddInternal);
}
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
{
var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
dependencies.CacheAs<IPreviewTrackOwner>(this);
if (registerAsOwner)
dependencies.CacheAs<IPreviewTrackOwner>(this);
return dependencies;
}
}
private class TestPreviewTrackManager : PreviewTrackManager
public class TestPreviewTrackManager : PreviewTrackManager
{
public bool AllowUpdate = true;
public new PreviewTrack CurrentTrack => base.CurrentTrack;
protected override TrackManagerPreviewTrack CreatePreviewTrack(BeatmapSetInfo beatmapSetInfo, ITrackStore trackStore) => new TestPreviewTrack(beatmapSetInfo, trackStore);
protected class TestPreviewTrack : TrackManagerPreviewTrack
public override bool UpdateSubTree()
{
if (!AllowUpdate)
return true;
return base.UpdateSubTree();
}
public class TestPreviewTrack : TrackManagerPreviewTrack
{
private readonly ITrackStore trackManager;
public new Track Track => base.Track;
public TestPreviewTrack(BeatmapSetInfo beatmapSetInfo, ITrackStore trackManager)
: base(beatmapSetInfo, trackManager)
{

View File

@ -0,0 +1,83 @@
// 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.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Testing;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Compose.Components;
using osuTK;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Editing
{
public class TestSceneBeatDivisorControl : OsuManualInputManagerTestScene
{
private BeatDivisorControl beatDivisorControl;
private BindableBeatDivisor bindableBeatDivisor;
private SliderBar<int> tickSliderBar;
private EquilateralTriangle tickMarkerHead;
[SetUp]
public void SetUp() => Schedule(() =>
{
Child = beatDivisorControl = new BeatDivisorControl(bindableBeatDivisor = new BindableBeatDivisor(16))
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(90, 90)
};
tickSliderBar = beatDivisorControl.ChildrenOfType<SliderBar<int>>().Single();
tickMarkerHead = tickSliderBar.ChildrenOfType<EquilateralTriangle>().Single();
});
[Test]
public void TestBindableBeatDivisor()
{
AddRepeatStep("move previous", () => bindableBeatDivisor.Previous(), 4);
AddAssert("divisor is 4", () => bindableBeatDivisor.Value == 4);
AddRepeatStep("move next", () => bindableBeatDivisor.Next(), 3);
AddAssert("divisor is 12", () => bindableBeatDivisor.Value == 12);
}
[Test]
public void TestMouseInput()
{
AddStep("hold marker", () =>
{
InputManager.MoveMouseTo(tickMarkerHead.ScreenSpaceDrawQuad.Centre);
InputManager.PressButton(MouseButton.Left);
});
AddStep("move to 8 and release", () =>
{
InputManager.MoveMouseTo(tickSliderBar.ScreenSpaceDrawQuad.Centre);
InputManager.ReleaseButton(MouseButton.Left);
});
AddAssert("divisor is 8", () => bindableBeatDivisor.Value == 8);
AddStep("hold marker", () => InputManager.PressButton(MouseButton.Left));
AddStep("move to 16", () => InputManager.MoveMouseTo(getPositionForDivisor(16)));
AddStep("move to ~10 and release", () =>
{
InputManager.MoveMouseTo(getPositionForDivisor(10));
InputManager.ReleaseButton(MouseButton.Left);
});
AddAssert("divisor clamped to 8", () => bindableBeatDivisor.Value == 8);
}
private Vector2 getPositionForDivisor(int divisor)
{
var relativePosition = (float)Math.Clamp(divisor, 0, 16) / 16;
var sliderDrawQuad = tickSliderBar.ScreenSpaceDrawQuad;
return new Vector2(
sliderDrawQuad.TopLeft.X + sliderDrawQuad.Width * relativePosition,
sliderDrawQuad.Centre.Y
);
}
}
}

View File

@ -0,0 +1,36 @@
// 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 NUnit.Framework;
using osu.Framework.Allocation;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Compose;
namespace osu.Game.Tests.Visual.Editing
{
[TestFixture]
public class TestSceneComposeScreen : EditorClockTestScene
{
[Cached(typeof(EditorBeatmap))]
[Cached(typeof(IBeatSnapProvider))]
private readonly EditorBeatmap editorBeatmap =
new EditorBeatmap(new OsuBeatmap
{
BeatmapInfo =
{
Ruleset = new OsuRuleset().RulesetInfo
}
});
[BackgroundDependencyLoader]
private void load()
{
Beatmap.Value = CreateWorkingBeatmap(editorBeatmap.PlayableBeatmap);
Child = new ComposeScreen();
}
}
}

View File

@ -0,0 +1,72 @@
// 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 osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Screens.Edit.Compose.Components;
using osuTK;
namespace osu.Game.Tests.Visual.Editing
{
public class TestSceneComposeSelectBox : OsuTestScene
{
private Container selectionArea;
public TestSceneComposeSelectBox()
{
SelectionBox selectionBox = null;
AddStep("create box", () =>
Child = selectionArea = new Container
{
Size = new Vector2(400),
Position = -new Vector2(150),
Anchor = Anchor.Centre,
Children = new Drawable[]
{
selectionBox = new SelectionBox
{
CanRotate = true,
CanScaleX = true,
CanScaleY = true,
OnRotation = handleRotation,
OnScale = handleScale
}
}
});
AddToggleStep("toggle rotation", state => selectionBox.CanRotate = state);
AddToggleStep("toggle x", state => selectionBox.CanScaleX = state);
AddToggleStep("toggle y", state => selectionBox.CanScaleY = state);
}
private bool handleScale(Vector2 amount, Anchor reference)
{
if ((reference & Anchor.y1) == 0)
{
int directionY = (reference & Anchor.y0) > 0 ? -1 : 1;
if (directionY < 0)
selectionArea.Y += amount.Y;
selectionArea.Height += directionY * amount.Y;
}
if ((reference & Anchor.x1) == 0)
{
int directionX = (reference & Anchor.x0) > 0 ? -1 : 1;
if (directionX < 0)
selectionArea.X += amount.X;
selectionArea.Width += directionX * amount.X;
}
return true;
}
private bool handleRotation(float angle)
{
// kinda silly and wrong, but just showing that the drag handles work.
selectionArea.Rotation += angle;
return true;
}
}
}

View File

@ -0,0 +1,172 @@
// 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 NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Compose.Components;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Tests.Visual.Editing
{
public class TestSceneDistanceSnapGrid : EditorClockTestScene
{
private const double beat_length = 100;
private static readonly Vector2 grid_position = new Vector2(512, 384);
[Cached(typeof(EditorBeatmap))]
private readonly EditorBeatmap editorBeatmap;
[Cached(typeof(IPositionSnapProvider))]
private readonly SnapProvider snapProvider = new SnapProvider();
public TestSceneDistanceSnapGrid()
{
editorBeatmap = new EditorBeatmap(new OsuBeatmap());
editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = beat_length });
}
[SetUp]
public void Setup() => Schedule(() =>
{
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.SlateGray
},
new TestDistanceSnapGrid()
};
});
[TestCase(1)]
[TestCase(2)]
[TestCase(3)]
[TestCase(4)]
[TestCase(6)]
[TestCase(8)]
[TestCase(12)]
[TestCase(16)]
public void TestBeatDivisor(int divisor)
{
AddStep($"set beat divisor = {divisor}", () => BeatDivisor.Value = divisor);
}
[Test]
public void TestLimitedDistance()
{
AddStep("create limited grid", () =>
{
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.SlateGray
},
new TestDistanceSnapGrid(100)
};
});
}
private class TestDistanceSnapGrid : DistanceSnapGrid
{
public new float DistanceSpacing => base.DistanceSpacing;
public TestDistanceSnapGrid(double? endTime = null)
: base(grid_position, 0, endTime)
{
}
protected override void CreateContent()
{
AddInternal(new Circle
{
Origin = Anchor.Centre,
Size = new Vector2(5),
Position = StartPosition
});
int indexFromPlacement = 0;
for (float s = StartPosition.X + DistanceSpacing; s <= DrawWidth && indexFromPlacement < MaxIntervals; s += DistanceSpacing, indexFromPlacement++)
{
AddInternal(new Circle
{
Origin = Anchor.Centre,
Size = new Vector2(5, 10),
Position = new Vector2(s, StartPosition.Y),
Colour = GetColourForIndexFromPlacement(indexFromPlacement)
});
}
indexFromPlacement = 0;
for (float s = StartPosition.X - DistanceSpacing; s >= 0 && indexFromPlacement < MaxIntervals; s -= DistanceSpacing, indexFromPlacement++)
{
AddInternal(new Circle
{
Origin = Anchor.Centre,
Size = new Vector2(5, 10),
Position = new Vector2(s, StartPosition.Y),
Colour = GetColourForIndexFromPlacement(indexFromPlacement)
});
}
indexFromPlacement = 0;
for (float s = StartPosition.Y + DistanceSpacing; s <= DrawHeight && indexFromPlacement < MaxIntervals; s += DistanceSpacing, indexFromPlacement++)
{
AddInternal(new Circle
{
Origin = Anchor.Centre,
Size = new Vector2(10, 5),
Position = new Vector2(StartPosition.X, s),
Colour = GetColourForIndexFromPlacement(indexFromPlacement)
});
}
indexFromPlacement = 0;
for (float s = StartPosition.Y - DistanceSpacing; s >= 0 && indexFromPlacement < MaxIntervals; s -= DistanceSpacing, indexFromPlacement++)
{
AddInternal(new Circle
{
Origin = Anchor.Centre,
Size = new Vector2(10, 5),
Position = new Vector2(StartPosition.X, s),
Colour = GetColourForIndexFromPlacement(indexFromPlacement)
});
}
}
public override (Vector2 position, double time) GetSnappedPosition(Vector2 screenSpacePosition)
=> (Vector2.Zero, 0);
}
private class SnapProvider : IPositionSnapProvider
{
public SnapResult SnapScreenSpacePositionToValidPosition(Vector2 screenSpacePosition) =>
new SnapResult(screenSpacePosition, null);
public SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) => new SnapResult(screenSpacePosition, 0);
public float GetBeatSnapDistanceAt(double referenceTime) => 10;
public float DurationToDistance(double referenceTime, double duration) => (float)duration;
public double DistanceToDuration(double referenceTime, float distance) => distance;
public double GetSnappedDurationFromDistance(double referenceTime, float distance) => 0;
public float GetSnappedDistanceFromDistance(double referenceTime, float distance) => 0;
}
}
}

View File

@ -0,0 +1,83 @@
// 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.IO;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Edit.Setup;
using osu.Game.Tests.Resources;
using SharpCompress.Archives;
using SharpCompress.Archives.Zip;
namespace osu.Game.Tests.Visual.Editing
{
public class TestSceneEditorBeatmapCreation : EditorTestScene
{
protected override Ruleset CreateEditorRuleset() => new OsuRuleset();
protected override bool EditorComponentsReady => Editor.ChildrenOfType<SetupScreen>().SingleOrDefault()?.IsLoaded == true;
[Resolved]
private BeatmapManager beatmapManager { get; set; }
public override void SetUpSteps()
{
AddStep("set dummy", () => Beatmap.Value = new DummyWorkingBeatmap(Audio, null));
base.SetUpSteps();
// if we save a beatmap with a hash collision, things fall over.
// probably needs a more solid resolution in the future but this will do for now.
AddStep("make new beatmap unique", () => EditorBeatmap.Metadata.Title = Guid.NewGuid().ToString());
}
[Test]
public void TestCreateNewBeatmap()
{
AddStep("save beatmap", () => Editor.Save());
AddAssert("new beatmap persisted", () => EditorBeatmap.BeatmapInfo.ID > 0);
AddAssert("new beatmap in database", () => beatmapManager.QueryBeatmapSet(s => s.ID == EditorBeatmap.BeatmapInfo.BeatmapSet.ID)?.DeletePending == false);
}
[Test]
public void TestExitWithoutSave()
{
AddStep("exit without save", () => Editor.Exit());
AddUntilStep("wait for exit", () => !Editor.IsCurrentScreen());
AddAssert("new beatmap not persisted", () => beatmapManager.QueryBeatmapSet(s => s.ID == EditorBeatmap.BeatmapInfo.BeatmapSet.ID)?.DeletePending == true);
}
[Test]
public void TestAddAudioTrack()
{
AddAssert("switch track to real track", () =>
{
var setup = Editor.ChildrenOfType<SetupScreen>().First();
var temp = TestResources.GetTestBeatmapForImport();
string extractedFolder = $"{temp}_extracted";
Directory.CreateDirectory(extractedFolder);
using (var zip = ZipArchive.Open(temp))
zip.WriteToDirectory(extractedFolder);
bool success = setup.ChildrenOfType<ResourcesSection>().First().ChangeAudioTrack(Path.Combine(extractedFolder, "03. Renatus - Soleily 192kbps.mp3"));
File.Delete(temp);
Directory.Delete(extractedFolder, true);
return success;
});
AddAssert("track length changed", () => Beatmap.Value.Track.Length > 60000);
}
}
}

View File

@ -0,0 +1,173 @@
// 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 NUnit.Framework;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Objects;
namespace osu.Game.Tests.Visual.Editing
{
public class TestSceneEditorChangeStates : EditorTestScene
{
protected override Ruleset CreateEditorRuleset() => new OsuRuleset();
[Test]
public void TestSelectedObjects()
{
HitCircle obj = null;
AddStep("add hitobject", () => EditorBeatmap.Add(obj = new HitCircle { StartTime = 1000 }));
AddStep("select hitobject", () => EditorBeatmap.SelectedHitObjects.Add(obj));
AddAssert("confirm 1 selected", () => EditorBeatmap.SelectedHitObjects.Count == 1);
AddStep("deselect hitobject", () => EditorBeatmap.SelectedHitObjects.Remove(obj));
AddAssert("confirm 0 selected", () => EditorBeatmap.SelectedHitObjects.Count == 0);
}
[Test]
public void TestUndoFromInitialState()
{
int hitObjectCount = 0;
AddStep("get initial state", () => hitObjectCount = EditorBeatmap.HitObjects.Count);
addUndoSteps();
AddAssert("no change occurred", () => hitObjectCount == EditorBeatmap.HitObjects.Count);
AddAssert("no unsaved changes", () => !Editor.HasUnsavedChanges);
}
[Test]
public void TestRedoFromInitialState()
{
int hitObjectCount = 0;
AddStep("get initial state", () => hitObjectCount = EditorBeatmap.HitObjects.Count);
addRedoSteps();
AddAssert("no change occurred", () => hitObjectCount == EditorBeatmap.HitObjects.Count);
AddAssert("no unsaved changes", () => !Editor.HasUnsavedChanges);
}
[Test]
public void TestAddObjectAndUndo()
{
HitObject addedObject = null;
HitObject removedObject = null;
HitObject expectedObject = null;
AddStep("bind removal", () =>
{
EditorBeatmap.HitObjectAdded += h => addedObject = h;
EditorBeatmap.HitObjectRemoved += h => removedObject = h;
});
AddStep("add hitobject", () => EditorBeatmap.Add(expectedObject = new HitCircle { StartTime = 1000 }));
AddAssert("hitobject added", () => addedObject == expectedObject);
AddAssert("unsaved changes", () => Editor.HasUnsavedChanges);
addUndoSteps();
AddAssert("hitobject removed", () => removedObject == expectedObject);
AddAssert("no unsaved changes", () => !Editor.HasUnsavedChanges);
}
[Test]
public void TestAddObjectThenUndoThenRedo()
{
HitObject addedObject = null;
HitObject removedObject = null;
HitObject expectedObject = null;
AddStep("bind removal", () =>
{
EditorBeatmap.HitObjectAdded += h => addedObject = h;
EditorBeatmap.HitObjectRemoved += h => removedObject = h;
});
AddStep("add hitobject", () => EditorBeatmap.Add(expectedObject = new HitCircle { StartTime = 1000 }));
addUndoSteps();
AddStep("reset variables", () =>
{
addedObject = null;
removedObject = null;
});
addRedoSteps();
AddAssert("hitobject added", () => addedObject.StartTime == expectedObject.StartTime); // Can't compare via equality (new hitobject instance)
AddAssert("no hitobject removed", () => removedObject == null);
AddAssert("unsaved changes", () => Editor.HasUnsavedChanges);
}
[Test]
public void TestAddObjectThenSaveHasNoUnsavedChanges()
{
AddStep("add hitobject", () => EditorBeatmap.Add(new HitCircle { StartTime = 1000 }));
AddAssert("unsaved changes", () => Editor.HasUnsavedChanges);
AddStep("save changes", () => Editor.Save());
AddAssert("no unsaved changes", () => !Editor.HasUnsavedChanges);
}
[Test]
public void TestRemoveObjectThenUndo()
{
HitObject addedObject = null;
HitObject removedObject = null;
HitObject expectedObject = null;
AddStep("bind removal", () =>
{
EditorBeatmap.HitObjectAdded += h => addedObject = h;
EditorBeatmap.HitObjectRemoved += h => removedObject = h;
});
AddStep("add hitobject", () => EditorBeatmap.Add(expectedObject = new HitCircle { StartTime = 1000 }));
AddStep("remove object", () => EditorBeatmap.Remove(expectedObject));
AddStep("reset variables", () =>
{
addedObject = null;
removedObject = null;
});
addUndoSteps();
AddAssert("hitobject added", () => addedObject.StartTime == expectedObject.StartTime); // Can't compare via equality (new hitobject instance)
AddAssert("no hitobject removed", () => removedObject == null);
AddAssert("unsaved changes", () => Editor.HasUnsavedChanges); // 2 steps performed, 1 undone
}
[Test]
public void TestRemoveObjectThenUndoThenRedo()
{
HitObject addedObject = null;
HitObject removedObject = null;
HitObject expectedObject = null;
AddStep("bind removal", () =>
{
EditorBeatmap.HitObjectAdded += h => addedObject = h;
EditorBeatmap.HitObjectRemoved += h => removedObject = h;
});
AddStep("add hitobject", () => EditorBeatmap.Add(expectedObject = new HitCircle { StartTime = 1000 }));
AddStep("remove object", () => EditorBeatmap.Remove(expectedObject));
addUndoSteps();
AddStep("reset variables", () =>
{
addedObject = null;
removedObject = null;
});
addRedoSteps();
AddAssert("hitobject removed", () => removedObject.StartTime == expectedObject.StartTime); // Can't compare via equality (new hitobject instance after undo)
AddAssert("no hitobject added", () => addedObject == null);
AddAssert("no changes", () => !Editor.HasUnsavedChanges); // end result is empty beatmap, matching original state
}
private void addUndoSteps() => AddStep("undo", () => Editor.Undo());
private void addRedoSteps() => AddStep("redo", () => Editor.Redo());
}
}

View File

@ -0,0 +1,154 @@
// 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.Game.Beatmaps;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Tests.Beatmaps;
using osuTK;
namespace osu.Game.Tests.Visual.Editing
{
public class TestSceneEditorClipboard : EditorTestScene
{
protected override Ruleset CreateEditorRuleset() => new OsuRuleset();
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false);
[Test]
public void TestCutRemovesObjects()
{
var addedObject = new HitCircle { StartTime = 1000 };
AddStep("add hitobject", () => EditorBeatmap.Add(addedObject));
AddStep("select added object", () => EditorBeatmap.SelectedHitObjects.Add(addedObject));
AddStep("cut hitobject", () => Editor.Cut());
AddAssert("no hitobjects in beatmap", () => EditorBeatmap.HitObjects.Count == 0);
}
[TestCase(1000)]
[TestCase(2000)]
public void TestCutPaste(double newTime)
{
var addedObject = new HitCircle { StartTime = 1000 };
AddStep("add hitobject", () => EditorBeatmap.Add(addedObject));
AddStep("select added object", () => EditorBeatmap.SelectedHitObjects.Add(addedObject));
AddStep("cut hitobject", () => Editor.Cut());
AddStep("move forward in time", () => EditorClock.Seek(newTime));
AddStep("paste hitobject", () => Editor.Paste());
AddAssert("is one object", () => EditorBeatmap.HitObjects.Count == 1);
AddAssert("new object selected", () => EditorBeatmap.SelectedHitObjects.Single().StartTime == newTime);
}
[Test]
public void TestCutPasteSlider()
{
var addedObject = new Slider
{
StartTime = 1000,
Path = new SliderPath
{
ControlPoints =
{
new PathControlPoint(),
new PathControlPoint(new Vector2(100, 0), PathType.Bezier)
}
}
};
AddStep("add hitobject", () => EditorBeatmap.Add(addedObject));
AddStep("select added object", () => EditorBeatmap.SelectedHitObjects.Add(addedObject));
AddStep("cut hitobject", () => Editor.Cut());
AddStep("paste hitobject", () => Editor.Paste());
AddAssert("is one object", () => EditorBeatmap.HitObjects.Count == 1);
AddAssert("path matches", () =>
{
var path = ((Slider)EditorBeatmap.HitObjects.Single()).Path;
return path.ControlPoints.Count == 2 && path.ControlPoints.SequenceEqual(addedObject.Path.ControlPoints);
});
}
[Test]
public void TestCutPasteSpinner()
{
var addedObject = new Spinner
{
StartTime = 1000,
Duration = 5000
};
AddStep("add hitobject", () => EditorBeatmap.Add(addedObject));
AddStep("select added object", () => EditorBeatmap.SelectedHitObjects.Add(addedObject));
AddStep("cut hitobject", () => Editor.Cut());
AddStep("paste hitobject", () => Editor.Paste());
AddAssert("is one object", () => EditorBeatmap.HitObjects.Count == 1);
AddAssert("duration matches", () => ((Spinner)EditorBeatmap.HitObjects.Single()).Duration == 5000);
}
[Test]
public void TestCopyPaste()
{
var addedObject = new HitCircle { StartTime = 1000 };
AddStep("add hitobject", () => EditorBeatmap.Add(addedObject));
AddStep("select added object", () => EditorBeatmap.SelectedHitObjects.Add(addedObject));
AddStep("copy hitobject", () => Editor.Copy());
AddStep("move forward in time", () => EditorClock.Seek(2000));
AddStep("paste hitobject", () => Editor.Paste());
AddAssert("are two objects", () => EditorBeatmap.HitObjects.Count == 2);
AddAssert("new object selected", () => EditorBeatmap.SelectedHitObjects.Single().StartTime == 2000);
}
[Test]
public void TestCutNothing()
{
AddStep("cut hitobject", () => Editor.Cut());
AddAssert("are no objects", () => EditorBeatmap.HitObjects.Count == 0);
}
[Test]
public void TestCopyNothing()
{
AddStep("copy hitobject", () => Editor.Copy());
AddAssert("are no objects", () => EditorBeatmap.HitObjects.Count == 0);
}
[Test]
public void TestPasteNothing()
{
AddStep("paste hitobject", () => Editor.Paste());
AddAssert("are no objects", () => EditorBeatmap.HitObjects.Count == 0);
}
}
}

View File

@ -1,19 +1,16 @@
// 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 NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Game.Screens.Edit.Components.RadioButtons;
namespace osu.Game.Tests.Visual.Editor
namespace osu.Game.Tests.Visual.Editing
{
[TestFixture]
public class TestSceneEditorComposeRadioButtons : OsuTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[] { typeof(DrawableRadioButton) };
public TestSceneEditorComposeRadioButtons()
{
RadioButtonCollection collection;
@ -26,7 +23,7 @@ namespace osu.Game.Tests.Visual.Editor
{
new RadioButton("Item 1", () => { }),
new RadioButton("Item 2", () => { }),
new RadioButton("Item 3", () => { }),
new RadioButton("Item 3", () => { }, () => new SpriteIcon { Icon = FontAwesome.Regular.Angry }),
new RadioButton("Item 4", () => { }),
new RadioButton("Item 5", () => { })
}

View File

@ -1,8 +1,6 @@
// 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 NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@ -10,13 +8,11 @@ using osu.Framework.Graphics.UserInterface;
using osu.Game.Graphics.UserInterface;
using osu.Game.Screens.Edit.Components.Menus;
namespace osu.Game.Tests.Visual.Editor
namespace osu.Game.Tests.Visual.Editing
{
[TestFixture]
public class TestSceneEditorMenuBar : OsuTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[] { typeof(EditorMenuBar), typeof(ScreenSelectionTabControl) };
public TestSceneEditorMenuBar()
{
Add(new Container

View File

@ -0,0 +1,88 @@
// 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.Testing;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
using osu.Game.Tests.Beatmaps;
using osu.Game.Screens.Edit.Compose.Components;
using osuTK;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Editing
{
public class TestSceneEditorQuickDelete : EditorTestScene
{
protected override Ruleset CreateEditorRuleset() => new OsuRuleset();
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false);
private BlueprintContainer blueprintContainer
=> Editor.ChildrenOfType<BlueprintContainer>().First();
[Test]
public void TestQuickDeleteRemovesObject()
{
var addedObject = new HitCircle { StartTime = 1000 };
AddStep("add hitobject", () => EditorBeatmap.Add(addedObject));
AddStep("select added object", () => EditorBeatmap.SelectedHitObjects.Add(addedObject));
AddStep("move mouse to object", () =>
{
var pos = blueprintContainer.ChildrenOfType<HitCirclePiece>().First().ScreenSpaceDrawQuad.Centre;
InputManager.MoveMouseTo(pos);
});
AddStep("hold shift", () => InputManager.PressKey(Key.ShiftLeft));
AddStep("right click", () => InputManager.Click(MouseButton.Right));
AddStep("release shift", () => InputManager.ReleaseKey(Key.ShiftLeft));
AddAssert("no hitobjects in beatmap", () => EditorBeatmap.HitObjects.Count == 0);
}
[Test]
public void TestQuickDeleteRemovesSliderControlPoint()
{
Slider slider = new Slider { StartTime = 1000 };
PathControlPoint[] points =
{
new PathControlPoint(),
new PathControlPoint(new Vector2(50, 0)),
new PathControlPoint(new Vector2(100, 0))
};
AddStep("add slider", () =>
{
slider.Path = new SliderPath(points);
EditorBeatmap.Add(slider);
});
AddStep("select added slider", () => EditorBeatmap.SelectedHitObjects.Add(slider));
AddStep("move mouse to controlpoint", () =>
{
var pos = blueprintContainer.ChildrenOfType<PathControlPointPiece>().ElementAt(1).ScreenSpaceDrawQuad.Centre;
InputManager.MoveMouseTo(pos);
});
AddStep("hold shift", () => InputManager.PressKey(Key.ShiftLeft));
AddStep("right click", () => InputManager.Click(MouseButton.Right));
AddAssert("slider has 2 points", () => slider.Path.ControlPoints.Count == 2);
// second click should nuke the object completely.
AddStep("right click", () => InputManager.Click(MouseButton.Right));
AddAssert("no hitobjects in beatmap", () => EditorBeatmap.HitObjects.Count == 0);
AddStep("release shift", () => InputManager.ReleaseKey(Key.ShiftLeft));
}
}
}

View File

@ -0,0 +1,50 @@
// 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.Graphics.Audio;
using osu.Framework.Testing;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Objects.Drawables;
namespace osu.Game.Tests.Visual.Editing
{
public class TestSceneEditorSamplePlayback : EditorTestScene
{
protected override Ruleset CreateEditorRuleset() => new OsuRuleset();
[Test]
public void TestSlidingSampleStopsOnSeek()
{
DrawableSlider slider = null;
DrawableSample[] loopingSamples = null;
DrawableSample[] onceOffSamples = null;
AddStep("get first slider", () =>
{
slider = Editor.ChildrenOfType<DrawableSlider>().OrderBy(s => s.HitObject.StartTime).First();
onceOffSamples = slider.ChildrenOfType<DrawableSample>().Where(s => !s.Looping).ToArray();
loopingSamples = slider.ChildrenOfType<DrawableSample>().Where(s => s.Looping).ToArray();
});
AddStep("start playback", () => EditorClock.Start());
AddUntilStep("wait for slider sliding then seek", () =>
{
if (!slider.Tracking.Value)
return false;
if (!loopingSamples.Any(s => s.Playing))
return false;
EditorClock.Seek(20000);
return true;
});
AddAssert("non-looping samples are playing", () => onceOffSamples.Length == 4 && loopingSamples.All(s => s.Played || s.Playing));
AddAssert("looping samples are not playing", () => loopingSamples.Length == 1 && loopingSamples.All(s => s.Played && !s.Playing));
}
}
}

View File

@ -13,7 +13,7 @@ using osu.Game.Rulesets.Osu.Objects;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Tests.Visual.Editor
namespace osu.Game.Tests.Visual.Editing
{
[TestFixture]
public class TestSceneEditorSeekSnapping : EditorClockTestScene
@ -28,18 +28,7 @@ namespace osu.Game.Tests.Visual.Editor
{
var testBeatmap = new Beatmap
{
ControlPointInfo = new ControlPointInfo
{
TimingPoints =
{
new TimingControlPoint { Time = 0, BeatLength = 200 },
new TimingControlPoint { Time = 100, BeatLength = 400 },
new TimingControlPoint { Time = 175, BeatLength = 800 },
new TimingControlPoint { Time = 350, BeatLength = 200 },
new TimingControlPoint { Time = 450, BeatLength = 100 },
new TimingControlPoint { Time = 500, BeatLength = 307.69230769230802 }
}
},
ControlPointInfo = new ControlPointInfo(),
HitObjects =
{
new HitCircle { StartTime = 0 },
@ -47,6 +36,13 @@ namespace osu.Game.Tests.Visual.Editor
}
};
testBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 200 });
testBeatmap.ControlPointInfo.Add(100, new TimingControlPoint { BeatLength = 400 });
testBeatmap.ControlPointInfo.Add(175, new TimingControlPoint { BeatLength = 800 });
testBeatmap.ControlPointInfo.Add(350, new TimingControlPoint { BeatLength = 200 });
testBeatmap.ControlPointInfo.Add(450, new TimingControlPoint { BeatLength = 100 });
testBeatmap.ControlPointInfo.Add(500, new TimingControlPoint { BeatLength = 307.69230769230802 });
Beatmap.Value = CreateWorkingBeatmap(testBeatmap);
Child = new TimingPointVisualiser(testBeatmap, 5000) { Clock = Clock };
@ -179,13 +175,13 @@ namespace osu.Game.Tests.Visual.Editor
AddAssert("Time = 50", () => Clock.CurrentTime == 50);
AddStep("Seek(49.999)", () => Clock.Seek(49.999));
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
AddAssert("Time = 50", () => Clock.CurrentTime == 50);
AddAssert("Time = 100", () => Clock.CurrentTime == 100);
AddStep("Seek(99)", () => Clock.Seek(99));
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
AddAssert("Time = 100", () => Clock.CurrentTime == 100);
AddStep("Seek(99.999)", () => Clock.Seek(99.999));
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
AddAssert("Time = 100", () => Clock.CurrentTime == 100);
AddAssert("Time = 100", () => Clock.CurrentTime == 150);
AddStep("Seek(174)", () => Clock.Seek(174));
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
AddAssert("Time = 175", () => Clock.CurrentTime == 175);

View File

@ -1,8 +1,6 @@
// 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 NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
@ -10,13 +8,11 @@ using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Edit.Components.Timelines.Summary;
using osuTK;
namespace osu.Game.Tests.Visual.Editor
namespace osu.Game.Tests.Visual.Editing
{
[TestFixture]
public class TestSceneEditorSummaryTimeline : EditorClockTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[] { typeof(SummaryTimeline) };
[BackgroundDependencyLoader]
private void load()
{

View File

@ -1,9 +1,7 @@
// 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 JetBrains.Annotations;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Timing;
@ -13,30 +11,15 @@ using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Edit;
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles;
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit.Compose.Components;
using osu.Game.Screens.Edit;
using osuTK;
namespace osu.Game.Tests.Visual.Editor
namespace osu.Game.Tests.Visual.Editing
{
[TestFixture]
public class TestSceneHitObjectComposer : OsuTestScene
public class TestSceneHitObjectComposer : EditorClockTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(SelectionHandler),
typeof(DragBox),
typeof(HitObjectComposer),
typeof(OsuHitObjectComposer),
typeof(BlueprintContainer),
typeof(NotNullAttribute),
typeof(HitCirclePiece),
typeof(HitCircleSelectionBlueprint),
typeof(HitCirclePlacementBlueprint),
};
[BackgroundDependencyLoader]
private void load()
{
@ -59,9 +42,13 @@ namespace osu.Game.Tests.Visual.Editor
},
});
var editorBeatmap = new EditorBeatmap(Beatmap.Value.GetPlayableBeatmap(new OsuRuleset().RulesetInfo));
var clock = new DecoupleableInterpolatingFramedClock { IsCoupled = false };
Dependencies.CacheAs<IAdjustableClock>(clock);
Dependencies.CacheAs<IFrameBasedClock>(clock);
Dependencies.CacheAs(editorBeatmap);
Dependencies.CacheAs<IBeatSnapProvider>(editorBeatmap);
Child = new OsuHitObjectComposer(new OsuRuleset());
}

View File

@ -4,12 +4,12 @@
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Components;
using osuTK;
namespace osu.Game.Tests.Visual.Editor
namespace osu.Game.Tests.Visual.Editing
{
[TestFixture]
public class TestScenePlaybackControl : OsuTestScene
@ -17,9 +17,8 @@ namespace osu.Game.Tests.Visual.Editor
[BackgroundDependencyLoader]
private void load()
{
var clock = new DecoupleableInterpolatingFramedClock { IsCoupled = false };
Dependencies.CacheAs<IAdjustableClock>(clock);
Dependencies.CacheAs<IFrameBasedClock>(clock);
var clock = new EditorClock { IsCoupled = false };
Dependencies.CacheAs(clock);
var playback = new PlaybackControl
{

View File

@ -0,0 +1,32 @@
// 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 NUnit.Framework;
using osu.Framework.Allocation;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Setup;
namespace osu.Game.Tests.Visual.Editing
{
[TestFixture]
public class TestSceneSetupScreen : EditorClockTestScene
{
[Cached(typeof(EditorBeatmap))]
[Cached(typeof(IBeatSnapProvider))]
private readonly EditorBeatmap editorBeatmap;
public TestSceneSetupScreen()
{
editorBeatmap = new EditorBeatmap(new OsuBeatmap());
}
[BackgroundDependencyLoader]
private void load()
{
Beatmap.Value = CreateWorkingBeatmap(editorBeatmap.PlayableBeatmap);
Child = new SetupScreen();
}
}
}

View File

@ -0,0 +1,21 @@
// 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 NUnit.Framework;
using osu.Framework.Graphics;
using osu.Game.Screens.Edit.Compose.Components.Timeline;
namespace osu.Game.Tests.Visual.Editing
{
[TestFixture]
public class TestSceneTimelineBlueprintContainer : TimelineTestScene
{
public override Drawable CreateTestComponent() => new TimelineBlueprintContainer(Composer);
protected override void LoadComplete()
{
base.LoadComplete();
Clock.Seek(10000);
}
}
}

View File

@ -0,0 +1,31 @@
// 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 NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Screens.Edit.Compose.Components;
using osuTK;
namespace osu.Game.Tests.Visual.Editing
{
[TestFixture]
public class TestSceneTimelineTickDisplay : TimelineTestScene
{
public override Drawable CreateTestComponent() => Empty(); // tick display is implicitly inside the timeline.
[BackgroundDependencyLoader]
private void load()
{
BeatDivisor.Value = 4;
Add(new BeatDivisorControl(BeatDivisor)
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
Margin = new MarginPadding(30),
Size = new Vector2(90)
});
}
}
}

View File

@ -0,0 +1,42 @@
// 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 NUnit.Framework;
using osu.Framework.Allocation;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Timing;
namespace osu.Game.Tests.Visual.Editing
{
[TestFixture]
public class TestSceneTimingScreen : EditorClockTestScene
{
[Cached(typeof(EditorBeatmap))]
[Cached(typeof(IBeatSnapProvider))]
private readonly EditorBeatmap editorBeatmap;
protected override bool ScrollUsingMouseWheel => false;
public TestSceneTimingScreen()
{
editorBeatmap = new EditorBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo));
}
[BackgroundDependencyLoader]
private void load()
{
Beatmap.Value = CreateWorkingBeatmap(editorBeatmap.PlayableBeatmap);
Beatmap.Disabled = true;
Child = new TimingScreen();
}
protected override void Dispose(bool isDisposing)
{
Beatmap.Disabled = false;
base.Dispose(isDisposing);
}
}
}

View File

@ -1,6 +1,7 @@
// 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.Threading;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
@ -14,7 +15,7 @@ using osu.Game.Graphics.Sprites;
using osu.Game.Rulesets.Osu;
using osuTK.Graphics;
namespace osu.Game.Tests.Visual.Editor
namespace osu.Game.Tests.Visual.Editing
{
[TestFixture]
public class TestSceneWaveform : OsuTestScene
@ -76,7 +77,7 @@ namespace osu.Game.Tests.Visual.Editor
};
});
AddUntilStep("wait for load", () => graph.ResampledWaveform != null);
AddUntilStep("wait for load", () => graph.Loaded.IsSet);
}
[Test]
@ -98,12 +99,18 @@ namespace osu.Game.Tests.Visual.Editor
};
});
AddUntilStep("wait for load", () => graph.ResampledWaveform != null);
AddUntilStep("wait for load", () => graph.Loaded.IsSet);
}
public class TestWaveformGraph : WaveformGraph
{
public new Waveform ResampledWaveform => base.ResampledWaveform;
public readonly ManualResetEventSlim Loaded = new ManualResetEventSlim();
protected override void OnWaveformRegenerated(Waveform waveform)
{
base.OnWaveformRegenerated(waveform);
Loaded.Set();
}
}
}
}

View File

@ -7,17 +7,18 @@ using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Primitives;
using osu.Framework.Graphics.Shapes;
using osu.Framework.MathUtils;
using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Graphics;
using osu.Game.Graphics.Cursor;
using osu.Game.Screens.Edit.Compose.Components.Timeline;
using osuTK;
using osuTK.Graphics;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Editor
namespace osu.Game.Tests.Visual.Editing
{
public class TestSceneZoomableScrollContainer : ManualInputManagerTestScene
public class TestSceneZoomableScrollContainer : OsuManualInputManagerTestScene
{
private ZoomableScrollContainer scrollContainer;
private Drawable innerBox;
@ -88,6 +89,7 @@ namespace osu.Game.Tests.Visual.Editor
// Scroll in at 0.25
AddStep("Move mouse to 0.25x", () => InputManager.MoveMouseTo(new Vector2(scrollQuad.TopLeft.X + 0.25f * scrollQuad.Size.X, scrollQuad.Centre.Y)));
AddStep("Press alt down", () => InputManager.PressKey(Key.AltLeft));
AddStep("Scroll by 3", () => InputManager.ScrollBy(new Vector2(0, 3)));
AddAssert("Box not at 0", () => !Precision.AlmostEquals(boxQuad.TopLeft, scrollQuad.TopLeft));
AddAssert("Box 1/4 at 1/4", () => Precision.AlmostEquals(boxQuad.TopLeft.X + 0.25f * boxQuad.Size.X, scrollQuad.TopLeft.X + 0.25f * scrollQuad.Size.X));
@ -96,6 +98,25 @@ namespace osu.Game.Tests.Visual.Editor
AddStep("Scroll by -3", () => InputManager.ScrollBy(new Vector2(0, -3)));
AddAssert("Box at 0", () => Precision.AlmostEquals(boxQuad.TopLeft, scrollQuad.TopLeft));
AddAssert("Box 1/4 at 1/4", () => Precision.AlmostEquals(boxQuad.TopLeft.X + 0.25f * boxQuad.Size.X, scrollQuad.TopLeft.X + 0.25f * scrollQuad.Size.X));
AddStep("Release alt", () => InputManager.ReleaseKey(Key.AltLeft));
}
[Test]
public void TestMouseZoomInThenScroll()
{
reset();
// Scroll in at 0.25
AddStep("Move mouse to 0.25x", () => InputManager.MoveMouseTo(new Vector2(scrollQuad.TopLeft.X + 0.25f * scrollQuad.Size.X, scrollQuad.Centre.Y)));
AddStep("Press alt down", () => InputManager.PressKey(Key.AltLeft));
AddStep("Zoom by 3", () => InputManager.ScrollBy(new Vector2(0, 3)));
AddStep("Release alt", () => InputManager.ReleaseKey(Key.AltLeft));
AddStep("Scroll far left", () => InputManager.ScrollBy(new Vector2(0, 30)));
AddUntilStep("Scroll is at start", () => Precision.AlmostEquals(scrollQuad.TopLeft.X, boxQuad.TopLeft.X, 1));
AddStep("Scroll far right", () => InputManager.ScrollBy(new Vector2(0, -300)));
AddUntilStep("Scroll is at end", () => Precision.AlmostEquals(scrollQuad.TopRight.X, boxQuad.TopRight.X, 1));
}
[Test]
@ -103,6 +124,8 @@ namespace osu.Game.Tests.Visual.Editor
{
reset();
AddStep("Press alt down", () => InputManager.PressKey(Key.AltLeft));
// Scroll in at 0.25
AddStep("Move mouse to 0.25x", () => InputManager.MoveMouseTo(new Vector2(scrollQuad.TopLeft.X + 0.25f * scrollQuad.Size.X, scrollQuad.Centre.Y)));
AddStep("Scroll by 1", () => InputManager.ScrollBy(new Vector2(0, 1)));
@ -124,6 +147,8 @@ namespace osu.Game.Tests.Visual.Editor
AddStep("Move mouse to 0.25x", () => InputManager.MoveMouseTo(new Vector2(scrollQuad.TopLeft.X + 0.25f * scrollQuad.Size.X, scrollQuad.Centre.Y)));
AddStep("Scroll by -1", () => InputManager.ScrollBy(new Vector2(0, -1)));
AddAssert("Box at 0", () => Precision.AlmostEquals(boxQuad.TopLeft, scrollQuad.TopLeft));
AddStep("Release alt", () => InputManager.ReleaseKey(Key.AltLeft));
}
private void reset()

View File

@ -1,42 +1,45 @@
// 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 NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets.Edit;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Compose.Components.Timeline;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Tests.Visual.Editor
namespace osu.Game.Tests.Visual.Editing
{
[TestFixture]
public class TestSceneEditorComposeTimeline : EditorClockTestScene
public abstract class TimelineTestScene : EditorClockTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(TimelineArea),
typeof(Timeline),
typeof(TimelineButton),
typeof(CentreMarker)
};
protected TimelineArea TimelineArea { get; private set; }
protected HitObjectComposer Composer { get; private set; }
[BackgroundDependencyLoader]
private void load(AudioManager audio)
{
Beatmap.Value = new WaveformTestBeatmap(audio);
Children = new Drawable[]
var playable = Beatmap.Value.GetPlayableBeatmap(Beatmap.Value.BeatmapInfo.Ruleset);
var editorBeatmap = new EditorBeatmap(playable);
Dependencies.Cache(editorBeatmap);
Dependencies.CacheAs<IBeatSnapProvider>(editorBeatmap);
Composer = playable.BeatmapInfo.Ruleset.CreateInstance().CreateHitObjectComposer().With(d => d.Alpha = 0);
AddRange(new Drawable[]
{
editorBeatmap,
Composer,
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
@ -48,22 +51,28 @@ namespace osu.Game.Tests.Visual.Editor
new AudioVisualiser(),
}
},
new TimelineArea
TimelineArea = new TimelineArea
{
Child = CreateTestComponent(),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.X,
Size = new Vector2(0.8f, 100)
Size = new Vector2(0.8f, 100),
}
};
});
}
public abstract Drawable CreateTestComponent();
private class AudioVisualiser : CompositeDrawable
{
private readonly Drawable marker;
private readonly IBindable<WorkingBeatmap> beatmap = new Bindable<WorkingBeatmap>();
private IAdjustableClock adjustableClock;
[Resolved]
private IBindable<WorkingBeatmap> beatmap { get; set; }
[Resolved]
private EditorClock editorClock { get; set; }
public AudioVisualiser()
{
@ -85,25 +94,20 @@ namespace osu.Game.Tests.Visual.Editor
};
}
[BackgroundDependencyLoader]
private void load(IAdjustableClock adjustableClock, IBindable<WorkingBeatmap> beatmap)
{
this.adjustableClock = adjustableClock;
this.beatmap.BindTo(beatmap);
}
protected override void Update()
{
base.Update();
if (beatmap.Value.Track.IsLoaded)
marker.X = (float)(adjustableClock.CurrentTime / beatmap.Value.Track.Length);
marker.X = (float)(editorClock.CurrentTime / beatmap.Value.Track.Length);
}
}
private class StartStopButton : Button
private class StartStopButton : OsuButton
{
private IAdjustableClock adjustableClock;
[Resolved]
private EditorClock editorClock { get; set; }
private bool started;
public StartStopButton()
@ -115,22 +119,16 @@ namespace osu.Game.Tests.Visual.Editor
Action = onClick;
}
[BackgroundDependencyLoader]
private void load(IAdjustableClock adjustableClock)
{
this.adjustableClock = adjustableClock;
}
private void onClick()
{
if (started)
{
adjustableClock.Stop();
editorClock.Stop();
Text = "Start";
}
else
{
adjustableClock.Start();
editorClock.Start();
Text = "Stop";
}

View File

@ -1,29 +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.
using System;
using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Compose.Components;
using osuTK;
namespace osu.Game.Tests.Visual.Editor
{
public class TestSceneBeatDivisorControl : OsuTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[] { typeof(BindableBeatDivisor) };
[BackgroundDependencyLoader]
private void load()
{
Child = new BeatDivisorControl(new BindableBeatDivisor())
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(90, 90)
};
}
}
}

View File

@ -1,25 +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.
using System;
using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Edit.Compose;
namespace osu.Game.Tests.Visual.Editor
{
[TestFixture]
public class TestSceneEditorCompose : EditorClockTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[] { typeof(ComposeScreen) };
[BackgroundDependencyLoader]
private void load()
{
Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
Child = new ComposeScreen();
}
}
}

View File

@ -0,0 +1,16 @@
// 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 osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
namespace osu.Game.Tests.Visual.Gameplay
{
/// <summary>
/// A <see cref="PlayerTestScene"/> with an arbitrary ruleset value to test with.
/// </summary>
public abstract class OsuPlayerTestScene : PlayerTestScene
{
protected override Ruleset CreatePlayerRuleset() => new OsuRuleset();
}
}

View File

@ -0,0 +1,85 @@
// 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.Game.Configuration;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Catch;
using osu.Game.Rulesets.Mania;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Taiko;
using osu.Game.Screens.Play;
namespace osu.Game.Tests.Visual.Gameplay
{
/// <summary>
/// A base class which runs <see cref="Player"/> test for all available rulesets.
/// Steps to be run for each ruleset should be added via <see cref="AddCheckSteps"/>.
/// </summary>
public abstract class TestSceneAllRulesetPlayers : RateAdjustedBeatmapTestScene
{
protected Player Player;
[BackgroundDependencyLoader]
private void load(RulesetStore rulesets)
{
OsuConfigManager manager;
Dependencies.Cache(manager = new OsuConfigManager(LocalStorage));
manager.GetBindable<double>(OsuSetting.DimLevel).Value = 1.0;
}
[Test]
public void TestOsu() => runForRuleset(new OsuRuleset().RulesetInfo);
[Test]
public void TestTaiko() => runForRuleset(new TaikoRuleset().RulesetInfo);
[Test]
public void TestCatch() => runForRuleset(new CatchRuleset().RulesetInfo);
[Test]
public void TestMania() => runForRuleset(new ManiaRuleset().RulesetInfo);
private void runForRuleset(RulesetInfo ruleset)
{
Player p = null;
AddStep($"load {ruleset.Name} player", () => p = loadPlayerFor(ruleset));
AddUntilStep("player loaded", () =>
{
if (p?.IsLoaded == true)
{
p = null;
return true;
}
return false;
});
AddCheckSteps();
}
protected abstract void AddCheckSteps();
private Player loadPlayerFor(RulesetInfo rulesetInfo)
{
Ruleset.Value = rulesetInfo;
var ruleset = rulesetInfo.CreateInstance();
var working = CreateWorkingBeatmap(rulesetInfo);
Beatmap.Value = working;
SelectedMods.Value = new[] { ruleset.GetAllMods().First(m => m is ModNoFail) };
Player = CreatePlayer(ruleset);
LoadScreen(Player);
return Player;
}
protected virtual Player CreatePlayer(Ruleset ruleset) => new TestPlayer(false, false);
}
}

View File

@ -3,36 +3,69 @@
using System.ComponentModel;
using System.Linq;
using osu.Framework.Testing;
using osu.Game.Beatmaps.Timing;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Play;
using osu.Game.Screens.Play.Break;
using osu.Game.Screens.Ranking;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Gameplay
{
[Description("Player instantiated with an autoplay mod.")]
public class TestSceneAutoplay : AllPlayersTestScene
public class TestSceneAutoplay : TestSceneAllRulesetPlayers
{
protected new TestPlayer Player => (TestPlayer)base.Player;
protected override Player CreatePlayer(Ruleset ruleset)
{
Mods.Value = Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() }).ToArray();
return new ScoreAccessiblePlayer();
SelectedMods.Value = new[] { ruleset.GetAutoplayMod() };
return new TestPlayer(false);
}
protected override void AddCheckSteps()
{
AddUntilStep("score above zero", () => ((ScoreAccessiblePlayer)Player).ScoreProcessor.TotalScore.Value > 0);
AddUntilStep("key counter counted keys", () => ((ScoreAccessiblePlayer)Player).HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 0));
AddUntilStep("score above zero", () => Player.ScoreProcessor.TotalScore.Value > 0);
AddUntilStep("key counter counted keys", () => Player.HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 2));
seekToBreak(0);
AddAssert("keys not counting", () => !Player.HUDOverlay.KeyCounter.IsCounting);
AddAssert("overlay displays 100% accuracy", () => Player.BreakOverlay.ChildrenOfType<BreakInfo>().Single().AccuracyDisplay.Current.Value == 1);
AddStep("rewind", () => Player.GameplayClockContainer.Seek(-80000));
AddUntilStep("key counter reset", () => Player.HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses == 0));
double? time = null;
AddStep("store time", () => time = Player.GameplayClockContainer.GameplayClock.CurrentTime);
// test seek via keyboard
AddStep("seek with right arrow key", () => InputManager.Key(Key.Right));
AddAssert("time seeked forward", () => Player.GameplayClockContainer.GameplayClock.CurrentTime > time + 2000);
AddStep("store time", () => time = Player.GameplayClockContainer.GameplayClock.CurrentTime);
AddStep("seek with left arrow key", () => InputManager.Key(Key.Left));
AddAssert("time seeked backward", () => Player.GameplayClockContainer.GameplayClock.CurrentTime < time);
seekToBreak(0);
seekToBreak(1);
AddStep("seek to completion", () => Player.GameplayClockContainer.Seek(Player.DrawableRuleset.Objects.Last().GetEndTime()));
AddUntilStep("results displayed", () => getResultsScreen() != null);
AddAssert("score has combo", () => getResultsScreen().Score.Combo > 100);
AddAssert("score has no misses", () => getResultsScreen().Score.Statistics[HitResult.Miss] == 0);
ResultsScreen getResultsScreen() => Stack.CurrentScreen as ResultsScreen;
}
private class ScoreAccessiblePlayer : TestPlayer
private void seekToBreak(int breakIndex)
{
public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
public new HUDOverlay HUDOverlay => base.HUDOverlay;
AddStep($"seek to break {breakIndex}", () => Player.GameplayClockContainer.Seek(destBreak().StartTime));
AddUntilStep("wait for seek to complete", () => Player.HUDOverlay.Progress.ReferenceClock.CurrentTime >= destBreak().StartTime);
public ScoreAccessiblePlayer()
: base(false, false)
{
}
BreakPeriod destBreak() => Beatmap.Value.Beatmap.Breaks.ElementAt(breakIndex);
}
}
}

View File

@ -1,10 +1,10 @@
// 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.Graphics;
using osu.Framework.Timing;
using osu.Game.Beatmaps.Timing;
using osu.Game.Screens.Play;
@ -12,32 +12,35 @@ using osu.Game.Screens.Play;
namespace osu.Game.Tests.Visual.Gameplay
{
[TestFixture]
public class TestSceneBreakOverlay : OsuTestScene
public class TestSceneBreakTracker : OsuTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(BreakOverlay),
};
private readonly BreakOverlay breakOverlay;
private readonly TestBreakOverlay breakOverlay;
private readonly TestBreakTracker breakTracker;
private readonly IReadOnlyList<BreakPeriod> testBreaks = new List<BreakPeriod>
{
new BreakPeriod
{
StartTime = 1000,
EndTime = 5000,
},
new BreakPeriod
{
StartTime = 6000,
EndTime = 13500,
},
new BreakPeriod(1000, 5000),
new BreakPeriod(6000, 13500),
};
public TestSceneBreakOverlay()
public TestSceneBreakTracker()
{
Add(breakOverlay = new TestBreakOverlay(true));
AddRange(new Drawable[]
{
breakTracker = new TestBreakTracker(),
breakOverlay = new BreakOverlay(true, null)
{
ProcessCustomClock = false,
}
});
}
protected override void Update()
{
base.Update();
breakOverlay.Clock = breakTracker.Clock;
}
[Test]
@ -53,7 +56,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestNoEffectsBreak()
{
var shortBreak = new BreakPeriod { EndTime = 500 };
var shortBreak = new BreakPeriod(0, 500);
setClock(true);
loadBreaksStep("short break", new[] { shortBreak });
@ -88,33 +91,43 @@ namespace osu.Game.Tests.Visual.Gameplay
loadBreaksStep("multiple breaks", testBreaks);
seekAndAssertBreak("seek to break start", testBreaks[1].StartTime, true);
AddAssert("is skipped to break #2", () => breakOverlay.CurrentBreakIndex == 1);
seekAndAssertBreak("seek to break middle", testBreaks[1].StartTime + testBreaks[1].Duration / 2, true);
seekAndAssertBreak("seek to break end", testBreaks[1].EndTime, false);
seekAndAssertBreak("seek to break after end", testBreaks[1].EndTime + 500, false);
}
[TestCase(true)]
[TestCase(false)]
public void TestBeforeGameplayStart(bool withBreaks)
{
setClock(true);
if (withBreaks)
loadBreaksStep("multiple breaks", testBreaks);
seekAndAssertBreak("seek to break intro time", -100, true);
seekAndAssertBreak("seek to break intro time", 0, false);
}
private void addShowBreakStep(double seconds)
{
AddStep($"show '{seconds}s' break", () => breakOverlay.Breaks = new List<BreakPeriod>
AddStep($"show '{seconds}s' break", () =>
{
new BreakPeriod
breakOverlay.Breaks = breakTracker.Breaks = new List<BreakPeriod>
{
StartTime = Clock.CurrentTime,
EndTime = Clock.CurrentTime + seconds * 1000,
}
new BreakPeriod(Clock.CurrentTime, Clock.CurrentTime + seconds * 1000)
};
});
}
private void setClock(bool useManual)
{
AddStep($"set {(useManual ? "manual" : "realtime")} clock", () => breakOverlay.SwitchClock(useManual));
AddStep($"set {(useManual ? "manual" : "realtime")} clock", () => breakTracker.SwitchClock(useManual));
}
private void loadBreaksStep(string breakDescription, IReadOnlyList<BreakPeriod> breaks)
{
AddStep($"load {breakDescription}", () => breakOverlay.Breaks = breaks);
AddStep($"load {breakDescription}", () => breakOverlay.Breaks = breakTracker.Breaks = breaks);
seekAndAssertBreak("seek back to 0", 0, false);
}
@ -138,42 +151,40 @@ namespace osu.Game.Tests.Visual.Gameplay
private void seekAndAssertBreak(string seekStepDescription, double time, bool shouldBeBreak)
{
AddStep(seekStepDescription, () => breakOverlay.ManualClockTime = time);
AddStep(seekStepDescription, () => breakTracker.ManualClockTime = time);
AddAssert($"is{(!shouldBeBreak ? " not" : string.Empty)} break time", () =>
{
breakOverlay.ProgressTime();
return breakOverlay.IsBreakTime.Value == shouldBeBreak;
breakTracker.ProgressTime();
return breakTracker.IsBreakTime.Value == shouldBeBreak;
});
}
private class TestBreakOverlay : BreakOverlay
private class TestBreakTracker : BreakTracker
{
private readonly FramedClock framedManualClock;
public readonly FramedClock FramedManualClock;
private readonly ManualClock manualClock;
private IFrameBasedClock originalClock;
public new int CurrentBreakIndex => base.CurrentBreakIndex;
public double ManualClockTime
{
get => manualClock.CurrentTime;
set => manualClock.CurrentTime = value;
}
public TestBreakOverlay(bool letterboxing)
: base(letterboxing)
public TestBreakTracker()
{
framedManualClock = new FramedClock(manualClock = new ManualClock());
FramedManualClock = new FramedClock(manualClock = new ManualClock());
ProcessCustomClock = false;
}
public void ProgressTime()
{
framedManualClock.ProcessFrame();
FramedManualClock.ProcessFrame();
Update();
}
public void SwitchClock(bool setManual) => Clock = setManual ? framedManualClock : originalClock;
public void SwitchClock(bool setManual) => Clock = setManual ? FramedManualClock : originalClock;
protected override void LoadComplete()
{

View File

@ -0,0 +1,47 @@
// 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.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Testing;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Play.HUD;
namespace osu.Game.Tests.Visual.Gameplay
{
public class TestSceneComboCounter : SkinnableTestScene
{
private IEnumerable<SkinnableComboCounter> comboCounters => CreatedDrawables.OfType<SkinnableComboCounter>();
protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset();
[SetUpSteps]
public void SetUpSteps()
{
AddStep("Create combo counters", () => SetContents(() =>
{
var comboCounter = new SkinnableComboCounter();
comboCounter.Current.Value = 1;
return comboCounter;
}));
}
[Test]
public void TestComboCounterIncrementing()
{
AddRepeatStep("increase combo", () =>
{
foreach (var counter in comboCounters)
counter.Current.Value++;
}, 10);
AddStep("reset combo", () =>
{
foreach (var counter in comboCounters)
counter.Current.Value = 0;
});
}
}
}

View File

@ -0,0 +1,126 @@
// 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 NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Storyboards;
using osuTK;
namespace osu.Game.Tests.Visual.Gameplay
{
public class TestSceneCompletionCancellation : OsuPlayerTestScene
{
[Resolved]
private AudioManager audio { get; set; }
private int resultsDisplayWaitCount =>
(int)((Screens.Play.Player.RESULTS_DISPLAY_DELAY / TimePerAction) * 2);
protected override bool AllowFail => false;
[SetUpSteps]
public override void SetUpSteps()
{
base.SetUpSteps();
// Ensure track has actually running before attempting to seek
AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning);
}
[Test]
public void TestCancelCompletionOnRewind()
{
complete();
cancel();
checkNoRanking();
}
[Test]
public void TestReCompleteAfterCancellation()
{
complete();
cancel();
complete();
AddUntilStep("attempted to push ranking", () => ((FakeRankingPushPlayer)Player).GotoRankingInvoked);
}
/// <summary>
/// Tests whether can still pause after cancelling completion by reverting <see cref="IScreen.ValidForResume"/> back to true.
/// </summary>
[Test]
public void TestCanPauseAfterCancellation()
{
complete();
cancel();
AddStep("pause", () => Player.Pause());
AddAssert("paused successfully", () => Player.GameplayClockContainer.IsPaused.Value);
checkNoRanking();
}
private void complete()
{
AddStep("seek to completion", () => Beatmap.Value.Track.Seek(5000));
AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value);
}
private void cancel()
{
AddStep("rewind to cancel", () => Beatmap.Value.Track.Seek(4000));
AddUntilStep("completion cleared by processor", () => !Player.ScoreProcessor.HasCompleted.Value);
}
private void checkNoRanking()
{
// wait to ensure there was no attempt of pushing the results screen.
AddWaitStep("wait", resultsDisplayWaitCount);
AddAssert("no attempt to push ranking", () => !((FakeRankingPushPlayer)Player).GotoRankingInvoked);
}
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
=> new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audio);
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
{
var beatmap = new Beatmap();
for (int i = 1; i <= 19; i++)
{
beatmap.HitObjects.Add(new HitCircle
{
Position = new Vector2(256, 192),
StartTime = i * 250,
});
}
return beatmap;
}
protected override TestPlayer CreatePlayer(Ruleset ruleset) => new FakeRankingPushPlayer();
public class FakeRankingPushPlayer : TestPlayer
{
public bool GotoRankingInvoked;
public FakeRankingPushPlayer()
: base(true, true)
{
}
protected override void GotoRanking()
{
GotoRankingInvoked = true;
}
}
}
}

View File

@ -4,12 +4,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using NUnit.Framework;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input;
using osu.Framework.MathUtils;
using osu.Framework.Utils;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
@ -25,6 +27,7 @@ using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;
using osuTK;
using osuTK.Graphics;
using JetBrains.Annotations;
namespace osu.Game.Tests.Visual.Gameplay
{
@ -43,10 +46,55 @@ namespace osu.Game.Tests.Visual.Gameplay
[SetUp]
public void Setup() => Schedule(() => testClock.CurrentTime = 0);
[TestCase("pooled")]
[TestCase("non-pooled")]
public void TestHitObjectLifetime(string pooled)
{
var beatmap = createBeatmap(_ => pooled == "pooled" ? new TestPooledHitObject() : new TestHitObject());
beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = time_range });
createTest(beatmap);
assertPosition(0, 0f);
assertDead(3);
setTime(3 * time_range);
assertPosition(3, 0f);
assertDead(0);
setTime(0 * time_range);
assertPosition(0, 0f);
assertDead(3);
}
[TestCase("pooled")]
[TestCase("non-pooled")]
public void TestNestedHitObject(string pooled)
{
var beatmap = createBeatmap(i =>
{
var h = pooled == "pooled" ? new TestPooledParentHitObject() : new TestParentHitObject();
h.Duration = 300;
h.ChildTimeOffset = i % 3 * 100;
return h;
});
beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = time_range });
createTest(beatmap);
assertPosition(0, 0f);
assertHeight(0);
assertChildPosition(0);
setTime(5 * time_range);
assertPosition(5, 0f);
assertHeight(5);
assertChildPosition(5);
}
[Test]
public void TestRelativeBeatLengthScaleSingleTimingPoint()
{
var beatmap = createBeatmap(new TimingControlPoint { BeatLength = time_range / 2 });
var beatmap = createBeatmap();
beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = time_range / 2 });
createTest(beatmap, d => d.RelativeScaleBeatLengthsOverride = true);
@ -60,10 +108,10 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestRelativeBeatLengthScaleTimingPointBeyondEndDoesNotBecomeDominant()
{
var beatmap = createBeatmap(
new TimingControlPoint { BeatLength = time_range / 2 },
new TimingControlPoint { Time = 12000, BeatLength = time_range },
new TimingControlPoint { Time = 100000, BeatLength = time_range });
var beatmap = createBeatmap();
beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = time_range / 2 });
beatmap.ControlPointInfo.Add(12000, new TimingControlPoint { BeatLength = time_range });
beatmap.ControlPointInfo.Add(100000, new TimingControlPoint { BeatLength = time_range });
createTest(beatmap, d => d.RelativeScaleBeatLengthsOverride = true);
@ -74,9 +122,9 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestRelativeBeatLengthScaleFromSecondTimingPoint()
{
var beatmap = createBeatmap(
new TimingControlPoint { BeatLength = time_range },
new TimingControlPoint { Time = 3 * time_range, BeatLength = time_range / 2 });
var beatmap = createBeatmap();
beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = time_range });
beatmap.ControlPointInfo.Add(3 * time_range, new TimingControlPoint { BeatLength = time_range / 2 });
createTest(beatmap, d => d.RelativeScaleBeatLengthsOverride = true);
@ -96,9 +144,9 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestNonRelativeScale()
{
var beatmap = createBeatmap(
new TimingControlPoint { BeatLength = time_range },
new TimingControlPoint { Time = 3 * time_range, BeatLength = time_range / 2 });
var beatmap = createBeatmap();
beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = time_range });
beatmap.ControlPointInfo.Add(3 * time_range, new TimingControlPoint { BeatLength = time_range / 2 });
createTest(beatmap);
@ -115,8 +163,65 @@ namespace osu.Game.Tests.Visual.Gameplay
assertPosition(4, 1f);
}
[Test]
public void TestSliderMultiplierDoesNotAffectRelativeBeatLength()
{
var beatmap = createBeatmap();
beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = time_range });
beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 2;
createTest(beatmap, d => d.RelativeScaleBeatLengthsOverride = true);
AddStep("adjust time range", () => drawableRuleset.TimeRange.Value = 5000);
for (int i = 0; i < 5; i++)
assertPosition(i, i / 5f);
}
[Test]
public void TestSliderMultiplierAffectsNonRelativeBeatLength()
{
var beatmap = createBeatmap();
beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = time_range });
beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 2;
createTest(beatmap);
AddStep("adjust time range", () => drawableRuleset.TimeRange.Value = 2000);
assertPosition(0, 0);
assertPosition(1, 1);
}
/// <summary>
/// Get a <see cref="DrawableTestHitObject" /> corresponding to the <paramref name="index"/>'th <see cref="TestHitObject"/>.
/// When the hit object is not alive, `null` is returned.
/// </summary>
[CanBeNull]
private DrawableTestHitObject getDrawableHitObject(int index)
{
var hitObject = drawableRuleset.Beatmap.HitObjects.ElementAt(index);
return (DrawableTestHitObject)drawableRuleset.Playfield.HitObjectContainer.AliveObjects.FirstOrDefault(obj => obj.HitObject == hitObject);
}
private float yScale => drawableRuleset.Playfield.HitObjectContainer.DrawHeight;
private void assertDead(int index) => AddAssert($"hitobject {index} is dead", () => getDrawableHitObject(index) == null);
private void assertHeight(int index) => AddAssert($"hitobject {index} height", () =>
{
var d = getDrawableHitObject(index);
return d != null && Precision.AlmostEquals(d.DrawHeight, yScale * (float)(d.HitObject.Duration / time_range), 0.1f);
});
private void assertChildPosition(int index) => AddAssert($"hitobject {index} child position", () =>
{
var d = getDrawableHitObject(index);
return d is DrawableTestParentHitObject && Precision.AlmostEquals(
d.NestedHitObjects.First().DrawPosition.Y,
yScale * (float)((TestParentHitObject)d.HitObject).ChildTimeOffset / time_range, 0.1f);
});
private void assertPosition(int index, float relativeY) => AddAssert($"hitobject {index} at {relativeY}",
() => Precision.AlmostEquals(drawableRuleset.Playfield.AllHitObjects.ElementAt(index).DrawPosition.Y, drawableRuleset.Playfield.HitObjectContainer.DrawHeight * relativeY));
() => Precision.AlmostEquals(getDrawableHitObject(index)?.DrawPosition.Y ?? -1, yScale * relativeY));
private void setTime(double time)
{
@ -127,16 +232,17 @@ namespace osu.Game.Tests.Visual.Gameplay
/// Creates an <see cref="IBeatmap"/>, containing 10 hitobjects and user-provided timing points.
/// The hitobjects are spaced <see cref="time_range"/> milliseconds apart.
/// </summary>
/// <param name="timingControlPoints">The timing points to add to the beatmap.</param>
/// <returns>The <see cref="IBeatmap"/>.</returns>
private IBeatmap createBeatmap(params TimingControlPoint[] timingControlPoints)
private IBeatmap createBeatmap(Func<int, TestHitObject> createAction = null)
{
var beatmap = new Beatmap<HitObject> { BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo } };
beatmap.ControlPointInfo.TimingPoints.AddRange(timingControlPoints);
var beatmap = new Beatmap<TestHitObject> { BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo } };
for (int i = 0; i < 10; i++)
beatmap.HitObjects.Add(new HitObject { StartTime = i * time_range });
{
var h = createAction?.Invoke(i) ?? new TestHitObject();
h.StartTime = i * time_range;
beatmap.HitObjects.Add(h);
}
return beatmap;
}
@ -145,7 +251,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{
var ruleset = new TestScrollingRuleset();
drawableRuleset = (TestDrawableScrollingRuleset)ruleset.CreateDrawableRulesetWith(CreateWorkingBeatmap(beatmap), Array.Empty<Mod>());
drawableRuleset = (TestDrawableScrollingRuleset)ruleset.CreateDrawableRulesetWith(CreateWorkingBeatmap(beatmap).GetPlayableBeatmap(ruleset.RulesetInfo));
drawableRuleset.FrameStablePlayback = false;
overrideAction?.Invoke(drawableRuleset);
@ -167,16 +273,11 @@ namespace osu.Game.Tests.Visual.Gameplay
private class TestScrollingRuleset : Ruleset
{
public TestScrollingRuleset(RulesetInfo rulesetInfo = null)
: base(rulesetInfo)
{
}
public override IEnumerable<Mod> GetModsFor(ModType type) => throw new NotImplementedException();
public override DrawableRuleset CreateDrawableRulesetWith(IWorkingBeatmap beatmap, IReadOnlyList<Mod> mods) => new TestDrawableScrollingRuleset(this, beatmap, mods);
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod> mods = null) => new TestDrawableScrollingRuleset(this, beatmap, mods);
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new TestBeatmapConverter(beatmap);
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new TestBeatmapConverter(beatmap, null);
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => throw new NotImplementedException();
@ -193,13 +294,29 @@ namespace osu.Game.Tests.Visual.Gameplay
protected override ScrollVisualisationMethod VisualisationMethod => ScrollVisualisationMethod.Overlapping;
public TestDrawableScrollingRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList<Mod> mods)
public new Bindable<double> TimeRange => base.TimeRange;
public TestDrawableScrollingRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod> mods = null)
: base(ruleset, beatmap, mods)
{
TimeRange.Value = time_range;
}
public override DrawableHitObject<TestHitObject> CreateDrawableRepresentation(TestHitObject h) => new DrawableTestHitObject(h);
public override DrawableHitObject<TestHitObject> CreateDrawableRepresentation(TestHitObject h)
{
switch (h)
{
case TestPooledHitObject _:
case TestPooledParentHitObject _:
return null;
case TestParentHitObject p:
return new DrawableTestParentHitObject(p);
default:
return new DrawableTestHitObject(h);
}
}
protected override PassThroughInputManager CreateInputManager() => new PassThroughInputManager();
@ -239,42 +356,61 @@ namespace osu.Game.Tests.Visual.Gameplay
}
}
});
RegisterPool<TestPooledHitObject, DrawableTestPooledHitObject>(1);
RegisterPool<TestPooledParentHitObject, DrawableTestPooledParentHitObject>(1);
}
}
private class TestBeatmapConverter : BeatmapConverter<TestHitObject>
{
public TestBeatmapConverter(IBeatmap beatmap)
: base(beatmap)
public TestBeatmapConverter(IBeatmap beatmap, Ruleset ruleset)
: base(beatmap, ruleset)
{
}
protected override IEnumerable<Type> ValidConversionTypes => new[] { typeof(HitObject) };
public override bool CanConvert() => true;
protected override IEnumerable<TestHitObject> ConvertHitObject(HitObject original, IBeatmap beatmap)
{
yield return new TestHitObject
{
StartTime = original.StartTime,
EndTime = (original as IHasEndTime)?.EndTime ?? (original.StartTime + 100)
};
}
protected override IEnumerable<TestHitObject> ConvertHitObject(HitObject original, IBeatmap beatmap, CancellationToken cancellationToken) =>
throw new NotImplementedException();
}
#endregion
#region HitObject
private class TestHitObject : HitObject, IHasEndTime
private class TestHitObject : HitObject, IHasDuration
{
public double EndTime { get; set; }
public double EndTime => StartTime + Duration;
public double Duration => EndTime - StartTime;
public double Duration { get; set; } = 100;
}
private class TestPooledHitObject : TestHitObject
{
}
private class TestParentHitObject : TestHitObject
{
public double ChildTimeOffset;
protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
{
AddNested(new TestHitObject { StartTime = StartTime + ChildTimeOffset });
}
}
private class TestPooledParentHitObject : TestParentHitObject
{
protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
{
AddNested(new TestPooledHitObject { StartTime = StartTime + ChildTimeOffset });
}
}
private class DrawableTestHitObject : DrawableHitObject<TestHitObject>
{
public DrawableTestHitObject(TestHitObject hitObject)
public DrawableTestHitObject([CanBeNull] TestHitObject hitObject)
: base(hitObject)
{
Anchor = Anchor.TopCentre;
@ -298,6 +434,52 @@ namespace osu.Game.Tests.Visual.Gameplay
}
});
}
protected override void Update() => LifetimeEnd = HitObject.EndTime;
}
private class DrawableTestPooledHitObject : DrawableTestHitObject
{
public DrawableTestPooledHitObject()
: base(null)
{
InternalChildren[0].Colour = Color4.LightSkyBlue;
InternalChildren[1].Colour = Color4.Blue;
}
}
private class DrawableTestParentHitObject : DrawableTestHitObject
{
private readonly Container<DrawableHitObject> container;
public DrawableTestParentHitObject([CanBeNull] TestHitObject hitObject)
: base(hitObject)
{
InternalChildren[0].Colour = Color4.LightYellow;
InternalChildren[1].Colour = Color4.Yellow;
AddInternal(container = new Container<DrawableHitObject>
{
RelativeSizeAxes = Axes.Both,
});
}
protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject) =>
new DrawableTestHitObject((TestHitObject)hitObject);
protected override void AddNestedHitObject(DrawableHitObject hitObject) => container.Add(hitObject);
protected override void ClearNestedHitObjects() => container.Clear(false);
}
private class DrawableTestPooledParentHitObject : DrawableTestParentHitObject
{
public DrawableTestPooledParentHitObject()
: base(null)
{
InternalChildren[0].Colour = Color4.LightSeaGreen;
InternalChildren[1].Colour = Color4.Green;
}
}
#endregion

View File

@ -0,0 +1,65 @@
// 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.Testing;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
using osu.Game.Skinning;
using osu.Game.Storyboards;
using osu.Game.Storyboards.Drawables;
using osuTK;
namespace osu.Game.Tests.Visual.Gameplay
{
public class TestSceneDrawableStoryboardSprite : SkinnableTestScene
{
protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset();
[Cached]
private Storyboard storyboard { get; set; } = new Storyboard();
[Test]
public void TestSkinSpriteDisallowedByDefault()
{
const string lookup_name = "hitcircleoverlay";
AddStep("allow skin lookup", () => storyboard.UseSkinSprites = false);
AddStep("create sprites", () => SetContents(
() => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero)));
assertSpritesFromSkin(false);
}
[Test]
public void TestAllowLookupFromSkin()
{
const string lookup_name = "hitcircleoverlay";
AddStep("allow skin lookup", () => storyboard.UseSkinSprites = true);
AddStep("create sprites", () => SetContents(
() => createSprite(lookup_name, Anchor.Centre, Vector2.Zero)));
assertSpritesFromSkin(true);
}
private DrawableStoryboardSprite createSprite(string lookupName, Anchor origin, Vector2 initialPosition)
=> new DrawableStoryboardSprite(
new StoryboardSprite(lookupName, origin, initialPosition)
).With(s =>
{
s.LifetimeStart = double.MinValue;
s.LifetimeEnd = double.MaxValue;
});
private void assertSpritesFromSkin(bool fromSkin) =>
AddAssert($"sprites are {(fromSkin ? "from skin" : "from storyboard")}",
() => this.ChildrenOfType<DrawableStoryboardSprite>()
.All(sprite => sprite.ChildrenOfType<SkinnableSprite>().Any() == fromSkin));
}
}

View File

@ -2,7 +2,6 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
@ -10,21 +9,14 @@ using osu.Game.Screens.Play;
namespace osu.Game.Tests.Visual.Gameplay
{
public class TestSceneFailAnimation : AllPlayersTestScene
public class TestSceneFailAnimation : TestSceneAllRulesetPlayers
{
protected override Player CreatePlayer(Ruleset ruleset)
{
Mods.Value = Array.Empty<Mod>();
SelectedMods.Value = Array.Empty<Mod>();
return new FailPlayer();
}
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(AllPlayersTestScene),
typeof(TestPlayer),
typeof(Player),
};
protected override void AddCheckSteps()
{
AddUntilStep("wait for fail", () => Player.HasFailed);
@ -43,7 +35,7 @@ namespace osu.Game.Tests.Visual.Gameplay
protected override void LoadComplete()
{
base.LoadComplete();
ScoreProcessor.FailConditions += (_, __) => true;
HealthProcessor.FailConditions += (_, __) => true;
}
}
}

View File

@ -6,46 +6,45 @@ using System.Linq;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
using osu.Game.Scoring;
using osu.Game.Screens.Play;
namespace osu.Game.Tests.Visual.Gameplay
{
public class TestSceneFailJudgement : AllPlayersTestScene
public class TestSceneFailJudgement : TestSceneAllRulesetPlayers
{
protected override Player CreatePlayer(Ruleset ruleset)
{
Mods.Value = Array.Empty<Mod>();
var beatmap = Beatmap.Value.GetPlayableBeatmap(ruleset.RulesetInfo, Array.Empty<Mod>());
return new FailPlayer(ruleset.GetAutoplayMod().CreateReplayScore(beatmap));
SelectedMods.Value = Array.Empty<Mod>();
return new FailPlayer();
}
protected override void AddCheckSteps()
{
AddUntilStep("wait for fail", () => Player.HasFailed);
AddUntilStep("wait for multiple judged objects", () => ((FailPlayer)Player).DrawableRuleset.Playfield.AllHitObjects.Count(h => h.AllJudged) > 1);
AddAssert("total judgements == 1", () => ((FailPlayer)Player).ScoreProcessor.JudgedHits == 1);
AddUntilStep("wait for multiple judgements", () => ((FailPlayer)Player).ScoreProcessor.JudgedHits > 1);
AddAssert("total number of results == 1", () =>
{
var score = new ScoreInfo();
((FailPlayer)Player).ScoreProcessor.PopulateScore(score);
return score.Statistics.Values.Sum() == 1;
});
}
private class FailPlayer : ReplayPlayer
private class FailPlayer : TestPlayer
{
public new DrawableRuleset DrawableRuleset => base.DrawableRuleset;
public new HealthProcessor HealthProcessor => base.HealthProcessor;
public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
protected override bool PauseOnFocusLost => false;
public FailPlayer(Score score)
: base(score, false, false)
public FailPlayer()
: base(false, false)
{
}
protected override void LoadComplete()
{
base.LoadComplete();
ScoreProcessor.FailConditions += (_, __) => true;
HealthProcessor.FailConditions += (_, __) => true;
}
}
}

View File

@ -0,0 +1,100 @@
// 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 NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Testing;
using osu.Game.Configuration;
using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Play.HUD;
namespace osu.Game.Tests.Visual.Gameplay
{
public class TestSceneFailingLayer : OsuTestScene
{
private FailingLayer layer;
private readonly Bindable<bool> showHealth = new Bindable<bool>();
[Resolved]
private OsuConfigManager config { get; set; }
[SetUpSteps]
public void SetUpSteps()
{
AddStep("create layer", () =>
{
Child = layer = new FailingLayer();
layer.BindHealthProcessor(new DrainingHealthProcessor(1));
layer.ShowHealth.BindTo(showHealth);
});
AddStep("show health", () => showHealth.Value = true);
AddStep("enable layer", () => config.Set(OsuSetting.FadePlayfieldWhenHealthLow, true));
AddUntilStep("layer is visible", () => layer.IsPresent);
}
[Test]
public void TestLayerFading()
{
AddSliderStep("current health", 0.0, 1.0, 1.0, val =>
{
if (layer != null)
layer.Current.Value = val;
});
AddStep("set health to 0.10", () => layer.Current.Value = 0.1);
AddUntilStep("layer fade is visible", () => layer.Child.Alpha > 0.1f);
AddStep("set health to 1", () => layer.Current.Value = 1f);
AddUntilStep("layer fade is invisible", () => !layer.Child.IsPresent);
}
[Test]
public void TestLayerDisabledViaConfig()
{
AddStep("disable layer", () => config.Set(OsuSetting.FadePlayfieldWhenHealthLow, false));
AddStep("set health to 0.10", () => layer.Current.Value = 0.1);
AddUntilStep("layer is not visible", () => !layer.IsPresent);
}
[Test]
public void TestLayerVisibilityWithAccumulatingProcessor()
{
AddStep("bind accumulating processor", () => layer.BindHealthProcessor(new AccumulatingHealthProcessor(1)));
AddStep("set health to 0.10", () => layer.Current.Value = 0.1);
AddUntilStep("layer is not visible", () => !layer.IsPresent);
}
[Test]
public void TestLayerVisibilityWithDrainingProcessor()
{
AddStep("bind accumulating processor", () => layer.BindHealthProcessor(new DrainingHealthProcessor(1)));
AddStep("set health to 0.10", () => layer.Current.Value = 0.1);
AddWaitStep("wait for potential fade", 10);
AddAssert("layer is still visible", () => layer.IsPresent);
}
[Test]
public void TestLayerVisibilityWithDifferentOptions()
{
AddStep("set health to 0.10", () => layer.Current.Value = 0.1);
AddStep("don't show health", () => showHealth.Value = false);
AddStep("disable FadePlayfieldWhenHealthLow", () => config.Set(OsuSetting.FadePlayfieldWhenHealthLow, false));
AddUntilStep("layer fade is invisible", () => !layer.IsPresent);
AddStep("don't show health", () => showHealth.Value = false);
AddStep("enable FadePlayfieldWhenHealthLow", () => config.Set(OsuSetting.FadePlayfieldWhenHealthLow, true));
AddUntilStep("layer fade is invisible", () => !layer.IsPresent);
AddStep("show health", () => showHealth.Value = true);
AddStep("disable FadePlayfieldWhenHealthLow", () => config.Set(OsuSetting.FadePlayfieldWhenHealthLow, false));
AddUntilStep("layer fade is invisible", () => !layer.IsPresent);
AddStep("show health", () => showHealth.Value = true);
AddStep("enable FadePlayfieldWhenHealthLow", () => config.Set(OsuSetting.FadePlayfieldWhenHealthLow, true));
AddUntilStep("layer fade is visible", () => layer.IsPresent);
}
}
}

View File

@ -2,7 +2,6 @@
// 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.Allocation;
@ -18,10 +17,8 @@ using osuTK.Input;
namespace osu.Game.Tests.Visual.Gameplay
{
[Description("player pause/fail screens")]
public class TestSceneGameplayMenuOverlay : ManualInputManagerTestScene
public class TestSceneGameplayMenuOverlay : OsuManualInputManagerTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[] { typeof(FailOverlay), typeof(PauseOverlay) };
private FailOverlay failOverlay;
private PauseOverlay pauseOverlay;
@ -89,7 +86,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{
showOverlay();
AddStep("Up arrow", () => press(Key.Up));
AddStep("Up arrow", () => InputManager.Key(Key.Up));
AddAssert("Last button selected", () => pauseOverlay.Buttons.Last().Selected.Value);
}
@ -101,7 +98,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{
showOverlay();
AddStep("Down arrow", () => press(Key.Down));
AddStep("Down arrow", () => InputManager.Key(Key.Down));
AddAssert("First button selected", () => getButton(0).Selected.Value);
}
@ -113,11 +110,11 @@ namespace osu.Game.Tests.Visual.Gameplay
{
AddStep("Show overlay", () => failOverlay.Show());
AddStep("Up arrow", () => press(Key.Up));
AddStep("Up arrow", () => InputManager.Key(Key.Up));
AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected.Value);
AddStep("Up arrow", () => press(Key.Up));
AddStep("Up arrow", () => InputManager.Key(Key.Up));
AddAssert("First button selected", () => failOverlay.Buttons.First().Selected.Value);
AddStep("Up arrow", () => press(Key.Up));
AddStep("Up arrow", () => InputManager.Key(Key.Up));
AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected.Value);
}
@ -129,11 +126,11 @@ namespace osu.Game.Tests.Visual.Gameplay
{
AddStep("Show overlay", () => failOverlay.Show());
AddStep("Down arrow", () => press(Key.Down));
AddStep("Down arrow", () => InputManager.Key(Key.Down));
AddAssert("First button selected", () => failOverlay.Buttons.First().Selected.Value);
AddStep("Down arrow", () => press(Key.Down));
AddStep("Down arrow", () => InputManager.Key(Key.Down));
AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected.Value);
AddStep("Down arrow", () => press(Key.Down));
AddStep("Down arrow", () => InputManager.Key(Key.Down));
AddAssert("First button selected", () => failOverlay.Buttons.First().Selected.Value);
}
@ -180,7 +177,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{
showOverlay();
AddStep("Down arrow", () => press(Key.Down));
AddStep("Down arrow", () => InputManager.Key(Key.Down));
AddStep("Hover second button", () => InputManager.MoveMouseTo(getButton(1)));
AddAssert("First button not selected", () => !getButton(0).Selected.Value);
AddAssert("Second button selected", () => getButton(1).Selected.Value);
@ -198,7 +195,7 @@ namespace osu.Game.Tests.Visual.Gameplay
});
AddStep("Hover second button", () => InputManager.MoveMouseTo(getButton(1)));
AddStep("Up arrow", () => press(Key.Up));
AddStep("Up arrow", () => InputManager.Key(Key.Up));
AddAssert("Second button not selected", () => !getButton(1).Selected.Value);
AddAssert("First button selected", () => getButton(0).Selected.Value);
}
@ -213,7 +210,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("Hover second button", () => InputManager.MoveMouseTo(getButton(1)));
AddStep("Unhover second button", () => InputManager.MoveMouseTo(Vector2.Zero));
AddStep("Down arrow", () => press(Key.Down));
AddStep("Down arrow", () => InputManager.Key(Key.Down));
AddAssert("First button selected", () => getButton(0).Selected.Value); // Initial state condition
}
@ -249,8 +246,8 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("Select second button", () =>
{
press(Key.Down);
press(Key.Down);
InputManager.Key(Key.Down);
InputManager.Key(Key.Down);
});
bool triggered = false;
@ -259,7 +256,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{
lastAction = pauseOverlay.OnRetry;
pauseOverlay.OnRetry = () => triggered = true;
press(Key.Enter);
InputManager.Key(Key.Enter);
});
AddAssert("Action was triggered", () =>
@ -275,16 +272,24 @@ namespace osu.Game.Tests.Visual.Gameplay
AddAssert("Overlay is closed", () => pauseOverlay.State.Value == Visibility.Hidden);
}
[Test]
public void TestSelectionResetOnVisibilityChange()
{
showOverlay();
AddStep("Select last button", () => InputManager.Key(Key.Up));
hideOverlay();
showOverlay();
AddAssert("No button selected",
() => pauseOverlay.Buttons.All(button => !button.Selected.Value));
}
private void showOverlay() => AddStep("Show overlay", () => pauseOverlay.Show());
private void hideOverlay() => AddStep("Hide overlay", () => pauseOverlay.Hide());
private DialogButton getButton(int index) => pauseOverlay.Buttons.Skip(index).First();
private void press(Key key)
{
InputManager.PressKey(key);
InputManager.ReleaseKey(key);
}
private void press(GlobalAction action)
{
globalActionContainer.TriggerPressed(action);

View File

@ -1,70 +1,54 @@
// 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.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Track;
using osu.Framework.MathUtils;
using osu.Framework.Utils;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.Play;
using osu.Game.Storyboards;
using osuTK;
namespace osu.Game.Tests.Visual.Gameplay
{
public class TestSceneGameplayRewinding : PlayerTestScene
public class TestSceneGameplayRewinding : OsuPlayerTestScene
{
private RulesetExposingPlayer player => (RulesetExposingPlayer)Player;
[Resolved]
private AudioManager audioManager { get; set; }
public TestSceneGameplayRewinding()
: base(new OsuRuleset())
{
}
private Track track;
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap)
{
var working = new ClockBackedTestWorkingBeatmap(beatmap, new FramedClock(new ManualClock { Rate = 1 }), audioManager);
track = working.Track;
return working;
}
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) =>
new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager);
[Test]
public void TestNoJudgementsOnRewind()
{
AddUntilStep("wait for track to start running", () => track.IsRunning);
AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning);
addSeekStep(3000);
AddAssert("all judged", () => player.DrawableRuleset.Playfield.AllHitObjects.All(h => h.Judged));
AddStep("clear results", () => player.AppliedResults.Clear());
AddAssert("all judged", () => Player.DrawableRuleset.Playfield.AllHitObjects.All(h => h.Judged));
AddUntilStep("key counter counted keys", () => Player.HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses >= 7));
AddStep("clear results", () => Player.Results.Clear());
addSeekStep(0);
AddAssert("none judged", () => player.DrawableRuleset.Playfield.AllHitObjects.All(h => !h.Judged));
AddAssert("no results triggered", () => player.AppliedResults.Count == 0);
AddAssert("none judged", () => Player.DrawableRuleset.Playfield.AllHitObjects.All(h => !h.Judged));
AddUntilStep("key counters reset", () => Player.HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses == 0));
AddAssert("no results triggered", () => Player.Results.Count == 0);
}
private void addSeekStep(double time)
{
AddStep($"seek to {time}", () => track.Seek(time));
AddStep($"seek to {time}", () => Beatmap.Value.Track.Seek(time));
// Allow a few frames of lenience
AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time, player.DrawableRuleset.FrameStableClock.CurrentTime, 100));
AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time, Player.DrawableRuleset.FrameStableClock.CurrentTime, 100));
}
protected override Player CreatePlayer(Ruleset ruleset)
protected override TestPlayer CreatePlayer(Ruleset ruleset)
{
Mods.Value = Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() }).ToArray();
return new RulesetExposingPlayer();
SelectedMods.Value = SelectedMods.Value.Concat(new[] { ruleset.GetAutoplayMod() }).ToArray();
return base.CreatePlayer(ruleset);
}
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
@ -85,25 +69,5 @@ namespace osu.Game.Tests.Visual.Gameplay
return beatmap;
}
private class RulesetExposingPlayer : Player
{
public readonly List<JudgementResult> AppliedResults = new List<JudgementResult>();
public new GameplayClockContainer GameplayClockContainer => base.GameplayClockContainer;
public new DrawableRuleset DrawableRuleset => base.DrawableRuleset;
public RulesetExposingPlayer()
: base(false, false)
{
}
[BackgroundDependencyLoader]
private void load()
{
ScoreProcessor.NewJudgement += r => AppliedResults.Add(r);
}
}
}
}

View File

@ -0,0 +1,69 @@
// 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.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics.Audio;
using osu.Framework.Testing;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Screens.Play;
using osu.Game.Skinning;
namespace osu.Game.Tests.Visual.Gameplay
{
public class TestSceneGameplaySamplePlayback : PlayerTestScene
{
[Test]
public void TestAllSamplesStopDuringSeek()
{
DrawableSlider slider = null;
DrawableSample[] samples = null;
ISamplePlaybackDisabler sampleDisabler = null;
AddUntilStep("get variables", () =>
{
sampleDisabler = Player;
slider = Player.ChildrenOfType<DrawableSlider>().OrderBy(s => s.HitObject.StartTime).FirstOrDefault();
samples = slider?.ChildrenOfType<DrawableSample>().ToArray();
return slider != null;
});
AddUntilStep("wait for slider sliding then seek", () =>
{
if (!slider.Tracking.Value)
return false;
if (!samples.Any(s => s.Playing))
return false;
Player.ChildrenOfType<GameplayClockContainer>().First().Seek(40000);
return true;
});
AddAssert("sample playback disabled", () => sampleDisabler.SamplePlaybackDisabled.Value);
// because we are in frame stable context, it's quite likely that not all samples are "played" at this point.
// the important thing is that at least one started, and that sample has since stopped.
AddAssert("all looping samples stopped immediately", () => allStopped(allLoopingSounds));
AddUntilStep("all samples stopped eventually", () => allStopped(allSounds));
AddAssert("sample playback still disabled", () => sampleDisabler.SamplePlaybackDisabled.Value);
AddUntilStep("seek finished, sample playback enabled", () => !sampleDisabler.SamplePlaybackDisabled.Value);
AddUntilStep("any sample is playing", () => Player.ChildrenOfType<PausableSkinnableSound>().Any(s => s.IsPlaying));
}
private IEnumerable<PausableSkinnableSound> allSounds => Player.ChildrenOfType<PausableSkinnableSound>();
private IEnumerable<PausableSkinnableSound> allLoopingSounds => allSounds.Where(sound => sound.Looping);
private bool allStopped(IEnumerable<PausableSkinnableSound> sounds) => sounds.All(sound => !sound.IsPlaying);
protected override bool Autoplay => true;
protected override Ruleset CreatePlayerRuleset() => new OsuRuleset();
}
}

View File

@ -0,0 +1,155 @@
// 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.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Game.Configuration;
using osu.Game.Rulesets.Mods;
using osu.Game.Screens.Play;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Gameplay
{
public class TestSceneHUDOverlay : OsuManualInputManagerTestScene
{
private HUDOverlay hudOverlay;
// best way to check without exposing.
private Drawable hideTarget => hudOverlay.KeyCounter;
private FillFlowContainer<KeyCounter> keyCounterFlow => hudOverlay.KeyCounter.ChildrenOfType<FillFlowContainer<KeyCounter>>().First();
[Resolved]
private OsuConfigManager config { get; set; }
[Test]
public void TestComboCounterIncrementing()
{
createNew();
AddRepeatStep("increase combo", () => { hudOverlay.ComboCounter.Current.Value++; }, 10);
AddStep("reset combo", () => { hudOverlay.ComboCounter.Current.Value = 0; });
}
[Test]
public void TestShownByDefault()
{
createNew();
AddAssert("showhud is set", () => hudOverlay.ShowHud.Value);
AddAssert("hidetarget is visible", () => hideTarget.IsPresent);
AddAssert("key counter flow is visible", () => keyCounterFlow.IsPresent);
AddAssert("pause button is visible", () => hudOverlay.HoldToQuit.IsPresent);
}
[Test]
public void TestFadesInOnLoadComplete()
{
float? initialAlpha = null;
createNew(h => h.OnLoadComplete += _ => initialAlpha = hideTarget.Alpha);
AddUntilStep("wait for load", () => hudOverlay.IsAlive);
AddAssert("initial alpha was less than 1", () => initialAlpha < 1);
}
[Test]
public void TestHideExternally()
{
createNew();
AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false);
AddUntilStep("hidetarget is hidden", () => !hideTarget.IsPresent);
AddAssert("pause button is still visible", () => hudOverlay.HoldToQuit.IsPresent);
// Key counter flow container should not be affected by this, only the key counter display will be hidden as checked above.
AddAssert("key counter flow not affected", () => keyCounterFlow.IsPresent);
}
[Test]
public void TestMomentaryShowHUD()
{
createNew();
HUDVisibilityMode originalConfigValue = HUDVisibilityMode.HideDuringGameplay;
AddStep("get original config value", () => originalConfigValue = config.Get<HUDVisibilityMode>(OsuSetting.HUDVisibilityMode));
AddStep("set hud to never show", () => config.Set(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Never));
AddUntilStep("wait for fade", () => !hideTarget.IsPresent);
AddStep("trigger momentary show", () => InputManager.PressKey(Key.ControlLeft));
AddUntilStep("wait for visible", () => hideTarget.IsPresent);
AddStep("stop trigering", () => InputManager.ReleaseKey(Key.ControlLeft));
AddUntilStep("wait for fade", () => !hideTarget.IsPresent);
AddStep("set original config value", () => config.Set(OsuSetting.HUDVisibilityMode, originalConfigValue));
}
[Test]
public void TestExternalHideDoesntAffectConfig()
{
HUDVisibilityMode originalConfigValue = HUDVisibilityMode.HideDuringGameplay;
createNew();
AddStep("get original config value", () => originalConfigValue = config.Get<HUDVisibilityMode>(OsuSetting.HUDVisibilityMode));
AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false);
AddAssert("config unchanged", () => originalConfigValue == config.Get<HUDVisibilityMode>(OsuSetting.HUDVisibilityMode));
AddStep("set showhud true", () => hudOverlay.ShowHud.Value = true);
AddAssert("config unchanged", () => originalConfigValue == config.Get<HUDVisibilityMode>(OsuSetting.HUDVisibilityMode));
}
[Test]
public void TestChangeHUDVisibilityOnHiddenKeyCounter()
{
bool keyCounterVisibleValue = false;
createNew();
AddStep("save keycounter visible value", () => keyCounterVisibleValue = config.Get<bool>(OsuSetting.KeyOverlay));
AddStep("set keycounter visible false", () =>
{
config.Set<bool>(OsuSetting.KeyOverlay, false);
hudOverlay.KeyCounter.AlwaysVisible.Value = false;
});
AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false);
AddUntilStep("hidetarget is hidden", () => !hideTarget.IsPresent);
AddAssert("key counters hidden", () => !keyCounterFlow.IsPresent);
AddStep("set showhud true", () => hudOverlay.ShowHud.Value = true);
AddUntilStep("hidetarget is visible", () => hideTarget.IsPresent);
AddAssert("key counters still hidden", () => !keyCounterFlow.IsPresent);
AddStep("return value", () => config.Set<bool>(OsuSetting.KeyOverlay, keyCounterVisibleValue));
}
private void createNew(Action<HUDOverlay> action = null)
{
AddStep("create overlay", () =>
{
hudOverlay = new HUDOverlay(null, null, null, Array.Empty<Mod>());
// Add any key just to display the key counter visually.
hudOverlay.KeyCounter.Add(new KeyCounterKeyboard(Key.Space));
hudOverlay.ComboCounter.Current.Value = 1;
action?.Invoke(hudOverlay);
Child = hudOverlay;
});
}
}
}

View File

@ -2,16 +2,15 @@
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
using osu.Game.Rulesets.Objects;
using System;
using System.Collections.Generic;
using osu.Game.Rulesets.Judgements;
using osu.Framework.MathUtils;
using osu.Framework.Utils;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Threading;
using osu.Game.Graphics.Sprites;
using osu.Game.Rulesets.Catch.Scoring;
using osu.Game.Rulesets.Mania.Scoring;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Scoring;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Scoring;
@ -19,18 +18,17 @@ using osu.Game.Screens.Play.HUD.HitErrorMeters;
namespace osu.Game.Tests.Visual.Gameplay
{
public class TestSceneBarHitErrorMeter : OsuTestScene
public class TestSceneHitErrorMeter : OsuTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(HitErrorMeter),
};
private HitErrorMeter meter;
private HitErrorMeter meter2;
private BarHitErrorMeter barMeter;
private BarHitErrorMeter barMeter2;
private BarHitErrorMeter barMeter3;
private ColourHitErrorMeter colourMeter;
private ColourHitErrorMeter colourMeter2;
private ColourHitErrorMeter colourMeter3;
private HitWindows hitWindows;
public TestSceneBarHitErrorMeter()
public TestSceneHitErrorMeter()
{
recreateDisplay(new OsuHitWindows(), 5);
@ -39,6 +37,22 @@ namespace osu.Game.Tests.Visual.Gameplay
AddRepeatStep("New max negative", () => newJudgement(-hitWindows.WindowFor(HitResult.Meh)), 20);
AddRepeatStep("New max positive", () => newJudgement(hitWindows.WindowFor(HitResult.Meh)), 20);
AddStep("New fixed judgement (50ms)", () => newJudgement(50));
AddStep("Judgement barrage", () =>
{
int runCount = 0;
ScheduledDelegate del = null;
del = Scheduler.AddDelayed(() =>
{
newJudgement(runCount++ / 10f);
if (runCount == 500)
// ReSharper disable once AccessToModifiedClosure
del?.Cancel();
}, 10, true);
});
}
[Test]
@ -85,23 +99,52 @@ namespace osu.Game.Tests.Visual.Gameplay
AutoSizeAxes = Axes.Both,
Children = new[]
{
new SpriteText { Text = $@"Great: {hitWindows?.WindowFor(HitResult.Great)}" },
new SpriteText { Text = $@"Good: {hitWindows?.WindowFor(HitResult.Good)}" },
new SpriteText { Text = $@"Meh: {hitWindows?.WindowFor(HitResult.Meh)}" },
new OsuSpriteText { Text = $@"Great: {hitWindows?.WindowFor(HitResult.Great)}" },
new OsuSpriteText { Text = $@"Good: {hitWindows?.WindowFor(HitResult.Ok)}" },
new OsuSpriteText { Text = $@"Meh: {hitWindows?.WindowFor(HitResult.Meh)}" },
}
});
Add(meter = new BarHitErrorMeter(hitWindows, true)
Add(barMeter = new BarHitErrorMeter(hitWindows, true)
{
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
});
Add(meter2 = new BarHitErrorMeter(hitWindows, false)
Add(barMeter2 = new BarHitErrorMeter(hitWindows, false)
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
});
Add(barMeter3 = new BarHitErrorMeter(hitWindows, true)
{
Anchor = Anchor.BottomCentre,
Origin = Anchor.CentreLeft,
Rotation = 270,
});
Add(colourMeter = new ColourHitErrorMeter(hitWindows)
{
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
Margin = new MarginPadding { Right = 50 }
});
Add(colourMeter2 = new ColourHitErrorMeter(hitWindows)
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Margin = new MarginPadding { Left = 50 }
});
Add(colourMeter3 = new ColourHitErrorMeter(hitWindows)
{
Anchor = Anchor.BottomCentre,
Origin = Anchor.CentreLeft,
Rotation = 270,
Margin = new MarginPadding { Left = 50 }
});
}
private void newJudgement(double offset = 0)
@ -112,8 +155,12 @@ namespace osu.Game.Tests.Visual.Gameplay
Type = HitResult.Perfect,
};
meter.OnNewJudgement(judgement);
meter2.OnNewJudgement(judgement);
barMeter.OnNewJudgement(judgement);
barMeter2.OnNewJudgement(judgement);
barMeter3.OnNewJudgement(judgement);
colourMeter.OnNewJudgement(judgement);
colourMeter2.OnNewJudgement(judgement);
colourMeter3.OnNewJudgement(judgement);
}
}
}

View File

@ -13,7 +13,7 @@ using osuTK.Input;
namespace osu.Game.Tests.Visual.Gameplay
{
[Description("'Hold to Quit' UI element")]
public class TestSceneHoldForMenuButton : ManualInputManagerTestScene
public class TestSceneHoldForMenuButton : OsuManualInputManagerTestScene
{
private bool exitAction;

View File

@ -0,0 +1,94 @@
// 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.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Bindings;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Input.Bindings;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.UI;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Gameplay
{
[HeadlessTest]
public class TestSceneKeyBindings : OsuManualInputManagerTestScene
{
private readonly ActionReceiver receiver;
public TestSceneKeyBindings()
{
Add(new TestKeyBindingContainer
{
Child = receiver = new ActionReceiver()
});
}
[Test]
public void TestDefaultsWhenNotDatabased()
{
AddStep("fire key", () => InputManager.Key(Key.A));
AddAssert("received key", () => receiver.ReceivedAction);
}
private class TestRuleset : Ruleset
{
public override IEnumerable<Mod> GetModsFor(ModType type) =>
throw new System.NotImplementedException();
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod> mods = null) =>
throw new System.NotImplementedException();
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) =>
throw new System.NotImplementedException();
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) =>
throw new System.NotImplementedException();
public override IEnumerable<KeyBinding> GetDefaultKeyBindings(int variant = 0)
{
return new[]
{
new KeyBinding(InputKey.A, TestAction.Down),
};
}
public override string Description => "test";
public override string ShortName => "test";
}
private enum TestAction
{
Down,
}
private class TestKeyBindingContainer : DatabasedKeyBindingContainer<TestAction>
{
public TestKeyBindingContainer()
: base(new TestRuleset().RulesetInfo, 0)
{
}
}
private class ActionReceiver : CompositeDrawable, IKeyBindingHandler<TestAction>
{
public bool ReceivedAction;
public bool OnPressed(TestAction action)
{
ReceivedAction = action == TestAction.Down;
return true;
}
public void OnReleased(TestAction action)
{
}
}
}
}

View File

@ -1,38 +1,29 @@
// 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.Graphics;
using osu.Framework.MathUtils;
using osu.Framework.Timing;
using osu.Framework.Utils;
using osu.Game.Screens.Play;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Gameplay
{
[TestFixture]
public class TestSceneKeyCounter : ManualInputManagerTestScene
public class TestSceneKeyCounter : OsuManualInputManagerTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(KeyCounterKeyboard),
typeof(KeyCounterMouse),
typeof(KeyCounterDisplay)
};
public TestSceneKeyCounter()
{
KeyCounterKeyboard rewindTestKeyCounterKeyboard;
KeyCounterKeyboard testCounter;
KeyCounterDisplay kc = new KeyCounterDisplay
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Children = new KeyCounter[]
{
rewindTestKeyCounterKeyboard = new KeyCounterKeyboard(Key.X),
testCounter = new KeyCounterKeyboard(Key.X),
new KeyCounterKeyboard(Key.X),
new KeyCounterMouse(MouseButton.Left),
new KeyCounterMouse(MouseButton.Right),
@ -44,59 +35,23 @@ namespace osu.Game.Tests.Visual.Gameplay
Key key = (Key)((int)Key.A + RNG.Next(26));
kc.Add(new KeyCounterKeyboard(key));
});
AddSliderStep("Fade time", 0, 200, 50, v => kc.FadeTime = v);
Key testKey = ((KeyCounterKeyboard)kc.Children.First()).Key;
double time1 = 0;
AddStep($"Press {testKey} key", () =>
void addPressKeyStep()
{
InputManager.PressKey(testKey);
InputManager.ReleaseKey(testKey);
});
AddStep($"Press {testKey} key", () => InputManager.Key(testKey));
}
AddAssert($"Check {testKey} counter after keypress", () => rewindTestKeyCounterKeyboard.CountPresses == 1);
AddStep($"Press {testKey} key", () =>
{
InputManager.PressKey(testKey);
InputManager.ReleaseKey(testKey);
time1 = Clock.CurrentTime;
});
AddAssert($"Check {testKey} counter after keypress", () => rewindTestKeyCounterKeyboard.CountPresses == 2);
IFrameBasedClock oldClock = null;
AddStep($"Rewind {testKey} counter once", () =>
{
oldClock = rewindTestKeyCounterKeyboard.Clock;
rewindTestKeyCounterKeyboard.Clock = new FramedOffsetClock(new FixedClock(time1 - 10));
});
AddAssert($"Check {testKey} counter after rewind", () => rewindTestKeyCounterKeyboard.CountPresses == 1);
AddStep($"Rewind {testKey} counter to zero", () => rewindTestKeyCounterKeyboard.Clock = new FramedOffsetClock(new FixedClock(0)));
AddAssert($"Check {testKey} counter after rewind", () => rewindTestKeyCounterKeyboard.CountPresses == 0);
AddStep("Restore clock", () => rewindTestKeyCounterKeyboard.Clock = oldClock);
addPressKeyStep();
AddAssert($"Check {testKey} counter after keypress", () => testCounter.CountPresses == 1);
addPressKeyStep();
AddAssert($"Check {testKey} counter after keypress", () => testCounter.CountPresses == 2);
AddStep("Disable counting", () => testCounter.IsCounting = false);
addPressKeyStep();
AddAssert($"Check {testKey} count has not changed", () => testCounter.CountPresses == 2);
Add(kc);
}
private class FixedClock : IClock
{
private readonly double time;
public FixedClock(double time)
{
this.time = time;
}
public double CurrentTime => time;
public double Rate => 1;
public bool IsRunning => false;
}
}
}

View File

@ -0,0 +1,113 @@
// 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.Diagnostics;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Graphics.Sprites;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Play;
using osu.Game.Storyboards;
using osu.Game.Tests.Beatmaps;
using osuTK;
namespace osu.Game.Tests.Visual.Gameplay
{
public class TestSceneLeadIn : RateAdjustedBeatmapTestScene
{
private LeadInPlayer player;
private const double lenience_ms = 10;
private const double first_hit_object = 2170;
[TestCase(1000, 0)]
[TestCase(2000, 0)]
[TestCase(3000, first_hit_object - 3000)]
[TestCase(10000, first_hit_object - 10000)]
public void TestLeadInProducesCorrectStartTime(double leadIn, double expectedStartTime)
{
loadPlayerWithBeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo)
{
BeatmapInfo = { AudioLeadIn = leadIn }
});
AddAssert($"first frame is {expectedStartTime}", () =>
{
Debug.Assert(player.FirstFrameClockTime != null);
return Precision.AlmostEquals(player.FirstFrameClockTime.Value, expectedStartTime, lenience_ms);
});
}
[TestCase(1000, 0)]
[TestCase(0, 0)]
[TestCase(-1000, -1000)]
[TestCase(-10000, -10000)]
public void TestStoryboardProducesCorrectStartTime(double firstStoryboardEvent, double expectedStartTime)
{
var storyboard = new Storyboard();
var sprite = new StoryboardSprite("unknown", Anchor.TopLeft, Vector2.Zero);
sprite.TimelineGroup.Alpha.Add(Easing.None, firstStoryboardEvent, firstStoryboardEvent + 500, 0, 1);
storyboard.GetLayer("Background").Add(sprite);
loadPlayerWithBeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo), storyboard);
AddAssert($"first frame is {expectedStartTime}", () =>
{
Debug.Assert(player.FirstFrameClockTime != null);
return Precision.AlmostEquals(player.FirstFrameClockTime.Value, expectedStartTime, lenience_ms);
});
}
private void loadPlayerWithBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
{
AddStep("create player", () =>
{
Beatmap.Value = CreateWorkingBeatmap(beatmap, storyboard);
LoadScreen(player = new LeadInPlayer());
});
AddUntilStep("player loaded", () => player.IsLoaded && player.Alpha == 1);
}
private class LeadInPlayer : TestPlayer
{
public LeadInPlayer()
: base(false, false)
{
}
public double? FirstFrameClockTime;
public new GameplayClockContainer GameplayClockContainer => base.GameplayClockContainer;
public double GameplayStartTime => DrawableRuleset.GameplayStartTime;
public double FirstHitObjectTime => DrawableRuleset.Objects.First().StartTime;
public double GameplayClockTime => GameplayClockContainer.GameplayClock.CurrentTime;
protected override void UpdateAfterChildren()
{
base.UpdateAfterChildren();
if (!FirstFrameClockTime.HasValue)
{
FirstFrameClockTime = GameplayClockContainer.GameplayClock.CurrentTime;
AddInternal(new OsuSpriteText
{
Text = $"GameplayStartTime: {DrawableRuleset.GameplayStartTime} "
+ $"FirstHitObjectTime: {FirstHitObjectTime} "
+ $"LeadInTime: {Beatmap.Value.BeatmapInfo.AudioLeadIn} "
+ $"FirstFrameClockTime: {FirstFrameClockTime}"
});
}
}
}
}
}

View File

@ -1,11 +1,8 @@
// 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 NUnit.Framework;
using osu.Game.Overlays;
using osu.Game.Overlays.MedalSplash;
using osu.Game.Users;
namespace osu.Game.Tests.Visual.Gameplay
@ -13,12 +10,6 @@ namespace osu.Game.Tests.Visual.Gameplay
[TestFixture]
public class TestSceneMedalOverlay : OsuTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(MedalOverlay),
typeof(DrawableMedal),
};
public TestSceneMedalOverlay()
{
AddStep(@"display", () =>

View File

@ -0,0 +1,31 @@
// 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 osu.Framework.Extensions.IEnumerableExtensions;
using osu.Game.Beatmaps.Timing;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu;
using osu.Game.Tests.Visual.UserInterface;
namespace osu.Game.Tests.Visual.Gameplay
{
public class TestSceneNightcoreBeatContainer : TestSceneBeatSyncedContainer
{
protected override void LoadComplete()
{
base.LoadComplete();
Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
Beatmap.Value.Track.Start();
Beatmap.Value.Track.Seek(Beatmap.Value.Beatmap.HitObjects.First().StartTime - 1000);
Add(new ModNightcore<HitObject>.NightcoreBeatContainer());
AddStep("change signature to quadruple", () => Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints.ForEach(p => p.TimeSignature = TimeSignatures.SimpleQuadruple));
AddStep("change signature to triple", () => Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints.ForEach(p => p.TimeSignature = TimeSignatures.SimpleTriple));
}
}
}

View File

@ -0,0 +1,72 @@
// 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.Bindables;
using osu.Game.Overlays;
using osu.Game.Rulesets;
namespace osu.Game.Tests.Visual.Gameplay
{
public class TestSceneOverlayActivation : OsuPlayerTestScene
{
protected new OverlayTestPlayer Player => base.Player as OverlayTestPlayer;
public override void SetUpSteps()
{
base.SetUpSteps();
AddUntilStep("gameplay has started",
() => Player.GameplayClockContainer.GameplayClock.CurrentTime > Player.DrawableRuleset.GameplayStartTime);
}
[Test]
public void TestGameplayOverlayActivation()
{
AddAssert("local user playing", () => Player.LocalUserPlaying.Value);
AddAssert("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled);
}
[Test]
public void TestGameplayOverlayActivationPaused()
{
AddAssert("local user playing", () => Player.LocalUserPlaying.Value);
AddAssert("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled);
AddStep("pause gameplay", () => Player.Pause());
AddAssert("local user not playing", () => !Player.LocalUserPlaying.Value);
AddUntilStep("activation mode is user triggered", () => Player.OverlayActivationMode == OverlayActivation.UserTriggered);
}
[Test]
public void TestGameplayOverlayActivationReplayLoaded()
{
AddAssert("local user playing", () => Player.LocalUserPlaying.Value);
AddAssert("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled);
AddStep("load a replay", () => Player.DrawableRuleset.HasReplayLoaded.Value = true);
AddAssert("local user not playing", () => !Player.LocalUserPlaying.Value);
AddAssert("activation mode is user triggered", () => Player.OverlayActivationMode == OverlayActivation.UserTriggered);
}
[Test]
public void TestGameplayOverlayActivationBreaks()
{
AddAssert("local user playing", () => Player.LocalUserPlaying.Value);
AddAssert("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled);
AddStep("seek to break", () => Player.GameplayClockContainer.Seek(Beatmap.Value.Beatmap.Breaks.First().StartTime));
AddUntilStep("activation mode is user triggered", () => Player.OverlayActivationMode == OverlayActivation.UserTriggered);
AddAssert("local user not playing", () => !Player.LocalUserPlaying.Value);
AddStep("seek to break end", () => Player.GameplayClockContainer.Seek(Beatmap.Value.Beatmap.Breaks.First().EndTime));
AddUntilStep("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled);
AddAssert("local user playing", () => Player.LocalUserPlaying.Value);
}
protected override TestPlayer CreatePlayer(Ruleset ruleset) => new OverlayTestPlayer();
protected class OverlayTestPlayer : TestPlayer
{
public new OverlayActivation OverlayActivationMode => base.OverlayActivationMode.Value;
public new Bindable<bool> LocalUserPlaying => base.LocalUserPlaying;
}
}
}

View File

@ -0,0 +1,39 @@
// 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 NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Textures;
using osu.Game.Graphics;
using osuTK;
namespace osu.Game.Tests.Visual.Gameplay
{
[TestFixture]
public class TestSceneParticleExplosion : OsuTestScene
{
private ParticleExplosion explosion;
[BackgroundDependencyLoader]
private void load(TextureStore textures)
{
AddStep("create initial", () =>
{
Child = explosion = new ParticleExplosion(textures.Get("Cursor/cursortrail"), 150, 1200)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(400)
};
});
AddWaitStep("wait for playback", 5);
AddRepeatStep(@"restart animation", () =>
{
explosion.Restart();
}, 10);
}
}
}

View File

@ -10,15 +10,14 @@ using osu.Framework.Testing;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Cursor;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Play;
using osu.Game.Skinning;
using osuTK;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Gameplay
{
public class TestScenePause : PlayerTestScene
public class TestScenePause : OsuPlayerTestScene
{
protected new PausePlayer Player => (PausePlayer)base.Player;
@ -27,7 +26,6 @@ namespace osu.Game.Tests.Visual.Gameplay
protected override Container<Drawable> Content => content;
public TestScenePause()
: base(new OsuRuleset())
{
base.Content.Add(content = new MenuCursorContainer { RelativeSizeAxes = Axes.Both });
}
@ -36,6 +34,7 @@ namespace osu.Game.Tests.Visual.Gameplay
public override void SetUpSteps()
{
base.SetUpSteps();
AddStep("resume player", () => Player.GameplayClockContainer.Start());
confirmClockRunning(true);
}
@ -52,7 +51,7 @@ namespace osu.Game.Tests.Visual.Gameplay
public void TestResumeWithResumeOverlay()
{
AddStep("move cursor to center", () => InputManager.MoveMouseTo(Player.ScreenSpaceDrawQuad.Centre));
AddUntilStep("wait for hitobjects", () => Player.ScoreProcessor.Health.Value < 1);
AddUntilStep("wait for hitobjects", () => Player.HealthProcessor.Health.Value < 1);
pauseAndConfirm();
resume();
@ -60,21 +59,35 @@ namespace osu.Game.Tests.Visual.Gameplay
confirmClockRunning(false);
confirmPauseOverlayShown(false);
AddStep("click to resume", () =>
{
InputManager.PressButton(MouseButton.Left);
InputManager.ReleaseButton(MouseButton.Left);
});
AddStep("click to resume", () => InputManager.Click(MouseButton.Left));
confirmClockRunning(true);
}
[Test]
public void TestPauseWithResumeOverlay()
{
AddStep("move cursor to center", () => InputManager.MoveMouseTo(Player.ScreenSpaceDrawQuad.Centre));
AddUntilStep("wait for hitobjects", () => Player.HealthProcessor.Health.Value < 1);
pauseAndConfirm();
resume();
confirmClockRunning(false);
confirmPauseOverlayShown(false);
pauseAndConfirm();
AddUntilStep("resume overlay is not active", () => Player.DrawableRuleset.ResumeOverlay.State.Value == Visibility.Hidden);
confirmPaused();
}
[Test]
public void TestResumeWithResumeOverlaySkipped()
{
AddStep("move cursor to button", () =>
InputManager.MoveMouseTo(Player.HUDOverlay.HoldToQuit.Children.OfType<HoldToConfirmContainer>().First().ScreenSpaceDrawQuad.Centre));
AddUntilStep("wait for hitobjects", () => Player.ScoreProcessor.Health.Value < 1);
AddUntilStep("wait for hitobjects", () => Player.HealthProcessor.Health.Value < 1);
pauseAndConfirm();
resumeAndConfirm();
@ -97,8 +110,9 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestExitTooSoon()
{
pauseAndConfirm();
AddStep("seek before gameplay", () => Player.GameplayClockContainer.Seek(-5000));
pauseAndConfirm();
resume();
AddStep("exit too soon", () => Player.Exit());
@ -128,13 +142,46 @@ namespace osu.Game.Tests.Visual.Gameplay
}
[Test]
public void TestExitFromGameplay()
public void TestExitFromFailedGameplay()
{
AddUntilStep("wait for fail", () => Player.HasFailed);
AddStep("exit", () => Player.Exit());
confirmPaused();
confirmExited();
}
exitAndConfirm();
[Test]
public void TestQuickRetryFromFailedGameplay()
{
AddUntilStep("wait for fail", () => Player.HasFailed);
AddStep("quick retry", () => Player.GameplayClockContainer.ChildrenOfType<HotkeyRetryOverlay>().First().Action?.Invoke());
confirmExited();
}
[Test]
public void TestQuickExitFromFailedGameplay()
{
AddUntilStep("wait for fail", () => Player.HasFailed);
AddStep("quick exit", () => Player.GameplayClockContainer.ChildrenOfType<HotkeyExitOverlay>().First().Action?.Invoke());
confirmExited();
}
[Test]
public void TestExitFromGameplay()
{
// an externally triggered exit should immediately exit, skipping all pause logic.
AddStep("exit", () => Player.Exit());
confirmExited();
}
[Test]
public void TestQuickExitFromGameplay()
{
AddStep("quick exit", () => Player.GameplayClockContainer.ChildrenOfType<HotkeyExitOverlay>().First().Action?.Invoke());
confirmExited();
}
[Test]
@ -160,6 +207,42 @@ namespace osu.Game.Tests.Visual.Gameplay
exitAndConfirm();
}
[Test]
public void TestRestartAfterResume()
{
AddStep("seek before gameplay", () => Player.GameplayClockContainer.Seek(-5000));
pauseAndConfirm();
resumeAndConfirm();
restart();
confirmExited();
}
[Test]
public void TestPauseSoundLoop()
{
AddStep("seek before gameplay", () => Player.GameplayClockContainer.Seek(-5000));
SkinnableSound getLoop() => Player.ChildrenOfType<PauseOverlay>().FirstOrDefault()?.ChildrenOfType<SkinnableSound>().FirstOrDefault();
pauseAndConfirm();
AddAssert("loop is playing", () => getLoop().IsPlaying);
resumeAndConfirm();
AddUntilStep("loop is stopped", () => !getLoop().IsPlaying);
AddUntilStep("pause again", () =>
{
Player.Pause();
return !Player.GameplayClockContainer.GameplayClock.IsRunning;
});
AddAssert("loop is playing", () => getLoop().IsPlaying);
resumeAndConfirm();
AddUntilStep("loop is stopped", () => !getLoop().IsPlaying);
}
private void pauseAndConfirm()
{
pause();
@ -177,6 +260,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("player not exited", () => Player.IsCurrentScreen());
AddStep("exit", () => Player.Exit());
confirmExited();
confirmNoTrackAdjustments();
}
private void confirmPaused()
@ -198,6 +282,12 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("player exited", () => !Player.IsCurrentScreen());
}
private void confirmNoTrackAdjustments()
{
AddAssert("track has no adjustments", () => Beatmap.Value.Track.AggregateFrequency.Value == 1);
}
private void restart() => AddStep("restart", () => Player.Restart());
private void pause() => AddStep("pause", () => Player.Pause());
private void resume() => AddStep("resume", () => Player.Resume());
@ -209,16 +299,10 @@ namespace osu.Game.Tests.Visual.Gameplay
protected override bool AllowFail => true;
protected override Player CreatePlayer(Ruleset ruleset) => new PausePlayer();
protected override TestPlayer CreatePlayer(Ruleset ruleset) => new PausePlayer();
protected class PausePlayer : TestPlayer
{
public new GameplayClockContainer GameplayClockContainer => base.GameplayClockContainer;
public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
public new HUDOverlay HUDOverlay => base.HUDOverlay;
public bool FailOverlayVisible => FailOverlay.State.Value == Visibility.Visible;
public bool PauseOverlayVisible => PauseOverlay.State.Value == Visibility.Visible;

View File

@ -0,0 +1,42 @@
// 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 NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Platform;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
namespace osu.Game.Tests.Visual.Gameplay
{
[HeadlessTest] // we alter unsafe properties on the game host to test inactive window state.
public class TestScenePauseWhenInactive : OsuPlayerTestScene
{
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
{
var beatmap = (Beatmap)base.CreateBeatmap(ruleset);
beatmap.HitObjects.RemoveAll(h => h.StartTime < 30000);
return beatmap;
}
[Resolved]
private GameHost host { get; set; }
[Test]
public void TestDoesntPauseDuringIntro()
{
AddStep("set inactive", () => ((Bindable<bool>)host.IsActive).Value = false);
AddStep("resume player", () => Player.GameplayClockContainer.Start());
AddAssert("ensure not paused", () => !Player.GameplayClockContainer.IsPaused.Value);
AddUntilStep("wait for pause", () => Player.GameplayClockContainer.IsPaused.Value);
AddAssert("time of pause is after gameplay start time", () => Player.GameplayClockContainer.GameplayClock.CurrentTime >= Player.DrawableRuleset.GameplayStartTime);
}
protected override TestPlayer CreatePlayer(Ruleset ruleset) => new TestPlayer(true, true, true);
}
}

View File

@ -5,57 +5,176 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Audio;
using osu.Framework.Graphics;
using osu.Framework.MathUtils;
using osu.Framework.Graphics.Containers;
using osu.Framework.Utils;
using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Game.Configuration;
using osu.Game.Graphics.Containers;
using osu.Game.Overlays;
using osu.Game.Overlays.Notifications;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using osu.Game.Screens;
using osu.Game.Screens.Play;
using osu.Game.Screens.Play.PlayerSettings;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Gameplay
{
public class TestScenePlayerLoader : ManualInputManagerTestScene
public class TestScenePlayerLoader : ScreenTestScene
{
private TestPlayerLoader loader;
private OsuScreenStack stack;
private TestPlayer player;
private bool epilepsyWarning;
[Resolved]
private AudioManager audioManager { get; set; }
[Resolved]
private SessionStatics sessionStatics { get; set; }
[Cached]
private readonly NotificationOverlay notificationOverlay;
[Cached]
private readonly VolumeOverlay volumeOverlay;
private readonly ChangelogOverlay changelogOverlay;
public TestScenePlayerLoader()
{
AddRange(new Drawable[]
{
notificationOverlay = new NotificationOverlay
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
},
volumeOverlay = new VolumeOverlay
{
Anchor = Anchor.TopLeft,
Origin = Anchor.TopLeft,
},
changelogOverlay = new ChangelogOverlay()
});
}
[SetUp]
public void Setup() => Schedule(() =>
{
InputManager.Child = stack = new OsuScreenStack { RelativeSizeAxes = Axes.Both };
Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
player = null;
audioManager.Volume.SetDefault();
});
/// <summary>
/// Sets the input manager child to a new test player loader container instance.
/// </summary>
/// <param name="interactive">If the test player should behave like the production one.</param>
/// <param name="beforeLoadAction">An action to run before player load but after bindable leases are returned.</param>
private void resetPlayer(bool interactive, Action beforeLoadAction = null)
{
beforeLoadAction?.Invoke();
Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
Beatmap.Value.BeatmapInfo.EpilepsyWarning = epilepsyWarning;
foreach (var mod in SelectedMods.Value.OfType<IApplicableToTrack>())
mod.ApplyToTrack(Beatmap.Value.Track);
LoadScreen(loader = new TestPlayerLoader(() => player = new TestPlayer(interactive, interactive)));
}
[Test]
public void TestEarlyExitBeforePlayerConstruction()
{
AddStep("load dummy beatmap", () => resetPlayer(false, () => SelectedMods.Value = new[] { new OsuModNightcore() }));
AddUntilStep("wait for current", () => loader.IsCurrentScreen());
AddStep("exit loader", () => loader.Exit());
AddUntilStep("wait for not current", () => !loader.IsCurrentScreen());
AddAssert("player did not load", () => player == null);
AddUntilStep("player disposed", () => loader.DisposalTask == null);
AddAssert("mod rate still applied", () => Beatmap.Value.Track.Rate != 1);
}
/// <summary>
/// When <see cref="PlayerLoader"/> exits early, it has to wait for the player load task
/// to complete before running disposal on player. This previously caused an issue where mod
/// speed adjustments were undone too late, causing cross-screen pollution.
/// </summary>
[Test]
public void TestEarlyExitAfterPlayerConstruction()
{
AddStep("load dummy beatmap", () => resetPlayer(false, () => SelectedMods.Value = new[] { new OsuModNightcore() }));
AddUntilStep("wait for current", () => loader.IsCurrentScreen());
AddAssert("mod rate applied", () => Beatmap.Value.Track.Rate != 1);
AddUntilStep("wait for non-null player", () => player != null);
AddStep("exit loader", () => loader.Exit());
AddUntilStep("wait for not current", () => !loader.IsCurrentScreen());
AddAssert("player did not load", () => !player.IsLoaded);
AddUntilStep("player disposed", () => loader.DisposalTask?.IsCompleted == true);
AddAssert("mod rate still applied", () => Beatmap.Value.Track.Rate != 1);
}
[Test]
public void TestBlockLoadViaMouseMovement()
{
AddStep("load dummy beatmap", () => stack.Push(loader = new TestPlayerLoader(() => new TestPlayer(false, false))));
AddStep("load dummy beatmap", () => resetPlayer(false));
AddUntilStep("wait for current", () => loader.IsCurrentScreen());
AddRepeatStep("move mouse", () => InputManager.MoveMouseTo(loader.VisualSettings.ScreenSpaceDrawQuad.TopLeft + (loader.VisualSettings.ScreenSpaceDrawQuad.BottomRight - loader.VisualSettings.ScreenSpaceDrawQuad.TopLeft) * RNG.NextSingle()), 20);
AddUntilStep("wait for load ready", () =>
{
moveMouse();
return player?.LoadState == LoadState.Ready;
});
AddRepeatStep("move mouse", moveMouse, 20);
AddAssert("loader still active", () => loader.IsCurrentScreen());
AddUntilStep("loads after idle", () => !loader.IsCurrentScreen());
void moveMouse()
{
InputManager.MoveMouseTo(
loader.VisualSettings.ScreenSpaceDrawQuad.TopLeft
+ (loader.VisualSettings.ScreenSpaceDrawQuad.BottomRight - loader.VisualSettings.ScreenSpaceDrawQuad.TopLeft)
* RNG.NextSingle());
}
}
[Test]
public void TestBlockLoadViaFocus()
{
AddStep("load dummy beatmap", () => resetPlayer(false));
AddUntilStep("wait for current", () => loader.IsCurrentScreen());
AddStep("show focused overlay", () => changelogOverlay.Show());
AddUntilStep("overlay visible", () => changelogOverlay.IsPresent);
AddUntilStep("wait for load ready", () => player?.LoadState == LoadState.Ready);
AddRepeatStep("twiddle thumbs", () => { }, 20);
AddAssert("loader still active", () => loader.IsCurrentScreen());
AddStep("hide overlay", () => changelogOverlay.Hide());
AddUntilStep("loads after idle", () => !loader.IsCurrentScreen());
}
[Test]
public void TestLoadContinuation()
{
Player player = null;
SlowLoadPlayer slowPlayer = null;
AddStep("load dummy beatmap", () => stack.Push(loader = new TestPlayerLoader(() => player = new TestPlayer(false, false))));
AddUntilStep("wait for current", () => loader.IsCurrentScreen());
AddStep("mouse in centre", () => InputManager.MoveMouseTo(loader.ScreenSpaceDrawQuad.Centre));
AddUntilStep("wait for player to be current", () => player.IsCurrentScreen());
AddStep("load slow dummy beatmap", () =>
{
stack.Push(loader = new TestPlayerLoader(() => slowPlayer = new SlowLoadPlayer(false, false)));
LoadScreen(loader = new TestPlayerLoader(() => slowPlayer = new SlowLoadPlayer(false, false)));
Scheduler.AddDelayed(() => slowPlayer.AllowLoad.Set(), 5000);
});
@ -65,16 +184,11 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestModReinstantiation()
{
TestPlayer player = null;
TestMod gameMod = null;
TestMod playerMod1 = null;
TestMod playerMod2 = null;
AddStep("load player", () =>
{
Mods.Value = new[] { gameMod = new TestMod() };
stack.Push(loader = new TestPlayerLoader(() => player = new TestPlayer()));
});
AddStep("load player", () => { resetPlayer(true, () => SelectedMods.Value = new[] { gameMod = new TestMod() }); });
AddUntilStep("wait for loader to become current", () => loader.IsCurrentScreen());
AddStep("mouse in centre", () => InputManager.MoveMouseTo(loader.ScreenSpaceDrawQuad.Centre));
@ -97,10 +211,105 @@ namespace osu.Game.Tests.Visual.Gameplay
AddAssert("player mods applied", () => playerMod2.Applied);
}
[Test]
public void TestModDisplayChanges()
{
var testMod = new TestMod();
AddStep("load player", () => resetPlayer(true));
AddUntilStep("wait for loader to become current", () => loader.IsCurrentScreen());
AddStep("set test mod in loader", () => loader.Mods.Value = new[] { testMod });
AddAssert("test mod is displayed", () => (TestMod)loader.DisplayedMods.Single() == testMod);
}
[Test]
public void TestMutedNotificationMasterVolume()
{
addVolumeSteps("master volume", () => audioManager.Volume.Value = 0, () => audioManager.Volume.IsDefault);
}
[Test]
public void TestMutedNotificationTrackVolume()
{
addVolumeSteps("music volume", () => audioManager.VolumeTrack.Value = 0, () => audioManager.VolumeTrack.IsDefault);
}
[Test]
public void TestMutedNotificationMuteButton()
{
addVolumeSteps("mute button", () => volumeOverlay.IsMuted.Value = true, () => !volumeOverlay.IsMuted.Value);
}
/// <remarks>
/// Created for avoiding copy pasting code for the same steps.
/// </remarks>
/// <param name="volumeName">What part of the volume system is checked</param>
/// <param name="beforeLoad">The action to be invoked to set the volume before loading</param>
/// <param name="assert">The function to be invoked and checked</param>
private void addVolumeSteps(string volumeName, Action beforeLoad, Func<bool> assert)
{
AddStep("reset notification lock", () => sessionStatics.GetBindable<bool>(Static.MutedAudioNotificationShownOnce).Value = false);
AddStep("load player", () => resetPlayer(false, beforeLoad));
AddUntilStep("wait for player", () => player?.LoadState == LoadState.Ready);
AddAssert("check for notification", () => notificationOverlay.UnreadCount.Value == 1);
AddStep("click notification", () =>
{
var scrollContainer = (OsuScrollContainer)notificationOverlay.Children.Last();
var flowContainer = scrollContainer.Children.OfType<FillFlowContainer<NotificationSection>>().First();
var notification = flowContainer.First();
InputManager.MoveMouseTo(notification);
InputManager.Click(MouseButton.Left);
});
AddAssert("check " + volumeName, assert);
AddUntilStep("wait for player load", () => player.IsLoaded);
}
[TestCase(true)]
[TestCase(false)]
public void TestEpilepsyWarning(bool warning)
{
AddStep("change epilepsy warning", () => epilepsyWarning = warning);
AddStep("load dummy beatmap", () => resetPlayer(false));
AddUntilStep("wait for current", () => loader.IsCurrentScreen());
AddAssert($"epilepsy warning {(warning ? "present" : "absent")}", () => this.ChildrenOfType<EpilepsyWarning>().Any() == warning);
if (warning)
{
AddUntilStep("sound volume decreased", () => Beatmap.Value.Track.AggregateVolume.Value == 0.25);
AddUntilStep("sound volume restored", () => Beatmap.Value.Track.AggregateVolume.Value == 1);
}
}
[Test]
public void TestEpilepsyWarningEarlyExit()
{
AddStep("set epilepsy warning", () => epilepsyWarning = true);
AddStep("load dummy beatmap", () => resetPlayer(false));
AddUntilStep("wait for current", () => loader.IsCurrentScreen());
AddUntilStep("wait for epilepsy warning", () => loader.ChildrenOfType<EpilepsyWarning>().Single().Alpha > 0);
AddStep("exit early", () => loader.Exit());
AddUntilStep("sound volume restored", () => Beatmap.Value.Track.AggregateVolume.Value == 1);
}
private class TestPlayerLoader : PlayerLoader
{
public new VisualSettings VisualSettings => base.VisualSettings;
public new Task DisposalTask => base.DisposalTask;
public IReadOnlyList<Mod> DisplayedMods => MetadataInfo.Mods.Value;
public TestPlayerLoader(Func<Player> createPlayer)
: base(createPlayer)
{
@ -123,17 +332,7 @@ namespace osu.Game.Tests.Visual.Gameplay
public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank;
}
private class TestPlayer : Visual.TestPlayer
{
public new Bindable<IReadOnlyList<Mod>> Mods => base.Mods;
public TestPlayer(bool allowPause = true, bool showResults = true)
: base(allowPause, showResults)
{
}
}
protected class SlowLoadPlayer : Visual.TestPlayer
protected class SlowLoadPlayer : TestPlayer
{
public readonly ManualResetEventSlim AllowLoad = new ManualResetEventSlim(false);

View File

@ -6,10 +6,11 @@ using osu.Framework.Lists;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
using osu.Game.Screens.Play;
using osu.Game.Storyboards;
namespace osu.Game.Tests.Visual.Gameplay
{
public class TestScenePlayerReferenceLeaking : AllPlayersTestScene
public class TestScenePlayerReferenceLeaking : TestSceneAllRulesetPlayers
{
private readonly WeakList<WorkingBeatmap> workingWeakReferences = new WeakList<WorkingBeatmap>();
@ -42,9 +43,9 @@ namespace osu.Game.Tests.Visual.Gameplay
});
}
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap)
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
{
var working = base.CreateWorkingBeatmap(beatmap);
var working = base.CreateWorkingBeatmap(beatmap, storyboard);
workingWeakReferences.Add(working);
return working;
}

View File

@ -0,0 +1,292 @@
// 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 System.Threading;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input;
using osu.Framework.Testing;
using osu.Framework.Timing;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Objects.Legacy;
using osu.Game.Rulesets.UI;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Tests.Visual.Gameplay
{
public class TestScenePoolingRuleset : OsuTestScene
{
private const double time_between_objects = 1000;
private TestDrawablePoolingRuleset drawableRuleset;
[Test]
public void TestReusedWithHitObjectsSpacedFarApart()
{
ManualClock clock = null;
createTest(new Beatmap
{
HitObjects =
{
new HitObject(),
new HitObject { StartTime = time_between_objects }
}
}, 1, () => new FramedClock(clock = new ManualClock()));
DrawableTestHitObject firstObject = null;
AddUntilStep("first object shown", () => this.ChildrenOfType<DrawableTestHitObject>().SingleOrDefault()?.HitObject == drawableRuleset.Beatmap.HitObjects[0]);
AddStep("get DHO", () => firstObject = this.ChildrenOfType<DrawableTestHitObject>().Single());
AddStep("fast forward to second object", () => clock.CurrentTime = drawableRuleset.Beatmap.HitObjects[1].StartTime);
AddUntilStep("second object shown", () => this.ChildrenOfType<DrawableTestHitObject>().SingleOrDefault()?.HitObject == drawableRuleset.Beatmap.HitObjects[1]);
AddAssert("DHO reused", () => this.ChildrenOfType<DrawableTestHitObject>().Single() == firstObject);
}
[Test]
public void TestCustomTransformsClearedBetweenReuses()
{
ManualClock clock = null;
createTest(new Beatmap
{
HitObjects =
{
new HitObject(),
new HitObject { StartTime = 2000 }
}
}, 1, () => new FramedClock(clock = new ManualClock()));
DrawableTestHitObject firstObject = null;
Vector2 position = default;
AddUntilStep("first object shown", () => this.ChildrenOfType<DrawableTestHitObject>().SingleOrDefault()?.HitObject == drawableRuleset.Beatmap.HitObjects[0]);
AddStep("get DHO", () => firstObject = this.ChildrenOfType<DrawableTestHitObject>().Single());
AddStep("store position", () => position = firstObject.Position);
AddStep("add custom transform", () => firstObject.ApplyCustomUpdateState += onStateUpdate);
AddStep("fast forward past first object", () => clock.CurrentTime = 1500);
AddStep("unapply custom transform", () => firstObject.ApplyCustomUpdateState -= onStateUpdate);
AddStep("fast forward to second object", () => clock.CurrentTime = drawableRuleset.Beatmap.HitObjects[1].StartTime);
AddUntilStep("second object shown", () => this.ChildrenOfType<DrawableTestHitObject>().SingleOrDefault()?.HitObject == drawableRuleset.Beatmap.HitObjects[1]);
AddAssert("DHO reused", () => this.ChildrenOfType<DrawableTestHitObject>().Single() == firstObject);
AddAssert("object in new position", () => firstObject.Position != position);
void onStateUpdate(DrawableHitObject hitObject, ArmedState state)
{
using (hitObject.BeginAbsoluteSequence(hitObject.StateUpdateTime))
hitObject.MoveToOffset(new Vector2(-100, 0));
}
}
[Test]
public void TestNotReusedWithHitObjectsSpacedClose()
{
ManualClock clock = null;
createTest(new Beatmap
{
HitObjects =
{
new HitObject(),
new HitObject { StartTime = 250 }
}
}, 2, () => new FramedClock(clock = new ManualClock()));
AddStep("fast forward to second object", () => clock.CurrentTime = drawableRuleset.Beatmap.HitObjects[1].StartTime);
AddUntilStep("two DHOs shown", () => this.ChildrenOfType<DrawableTestHitObject>().Count() == 2);
AddAssert("DHOs have different hitobjects",
() => this.ChildrenOfType<DrawableTestHitObject>().ElementAt(0).HitObject != this.ChildrenOfType<DrawableTestHitObject>().ElementAt(1).HitObject);
}
[Test]
public void TestManyHitObjects()
{
var beatmap = new Beatmap();
for (int i = 0; i < 500; i++)
beatmap.HitObjects.Add(new HitObject { StartTime = i * 10 });
createTest(beatmap, 100);
AddUntilStep("any DHOs shown", () => this.ChildrenOfType<DrawableTestHitObject>().Any());
AddUntilStep("no DHOs shown", () => !this.ChildrenOfType<DrawableTestHitObject>().Any());
}
private void createTest(IBeatmap beatmap, int poolSize, Func<IFrameBasedClock> createClock = null) => AddStep("create test", () =>
{
var ruleset = new TestPoolingRuleset();
drawableRuleset = (TestDrawablePoolingRuleset)ruleset.CreateDrawableRulesetWith(CreateWorkingBeatmap(beatmap).GetPlayableBeatmap(ruleset.RulesetInfo));
drawableRuleset.FrameStablePlayback = true;
drawableRuleset.PoolSize = poolSize;
Child = new Container
{
RelativeSizeAxes = Axes.Both,
Clock = createClock?.Invoke() ?? new FramedOffsetClock(Clock, false) { Offset = -Clock.CurrentTime },
Child = drawableRuleset
};
});
#region Ruleset
private class TestPoolingRuleset : Ruleset
{
public override IEnumerable<Mod> GetModsFor(ModType type) => throw new NotImplementedException();
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod> mods = null) => new TestDrawablePoolingRuleset(this, beatmap, mods);
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new TestBeatmapConverter(beatmap, this);
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => throw new NotImplementedException();
public override string Description { get; } = string.Empty;
public override string ShortName { get; } = string.Empty;
}
private class TestDrawablePoolingRuleset : DrawableRuleset<TestHitObject>
{
public int PoolSize;
public TestDrawablePoolingRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod> mods = null)
: base(ruleset, beatmap, mods)
{
}
public override DrawableHitObject<TestHitObject> CreateDrawableRepresentation(TestHitObject h) => null;
protected override PassThroughInputManager CreateInputManager() => new PassThroughInputManager();
protected override Playfield CreatePlayfield() => new TestPlayfield(PoolSize);
}
private class TestPlayfield : Playfield
{
private readonly int poolSize;
public TestPlayfield(int poolSize)
{
this.poolSize = poolSize;
AddInternal(HitObjectContainer);
}
[BackgroundDependencyLoader]
private void load()
{
RegisterPool<TestHitObject, DrawableTestHitObject>(poolSize);
}
protected override HitObjectLifetimeEntry CreateLifetimeEntry(HitObject hitObject) => new TestHitObjectLifetimeEntry(hitObject);
protected override GameplayCursorContainer CreateCursor() => null;
}
private class TestHitObjectLifetimeEntry : HitObjectLifetimeEntry
{
public TestHitObjectLifetimeEntry(HitObject hitObject)
: base(hitObject)
{
}
protected override double InitialLifetimeOffset => 0;
}
private class TestBeatmapConverter : BeatmapConverter<TestHitObject>
{
public TestBeatmapConverter(IBeatmap beatmap, Ruleset ruleset)
: base(beatmap, ruleset)
{
}
public override bool CanConvert() => true;
protected override IEnumerable<TestHitObject> ConvertHitObject(HitObject original, IBeatmap beatmap, CancellationToken cancellationToken)
{
yield return new TestHitObject
{
StartTime = original.StartTime,
Duration = 250
};
}
}
#endregion
#region HitObject
private class TestHitObject : ConvertHitObject
{
public double EndTime => StartTime + Duration;
public double Duration { get; set; }
}
private class DrawableTestHitObject : DrawableHitObject<TestHitObject>
{
public DrawableTestHitObject()
: base(null)
{
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
Size = new Vector2(50, 50);
Colour = new Color4(RNG.NextSingle(), RNG.NextSingle(), RNG.NextSingle(), 1f);
}
[BackgroundDependencyLoader]
private void load()
{
AddInternal(new Circle
{
RelativeSizeAxes = Axes.Both,
});
}
protected override void OnApply()
{
base.OnApply();
Position = new Vector2(RNG.Next(-200, 200), RNG.Next(-200, 200));
}
protected override void CheckForResult(bool userTriggered, double timeOffset)
{
if (timeOffset > HitObject.Duration)
ApplyResult(r => r.Type = r.Judgement.MaxResult);
}
protected override void UpdateHitStateTransforms(ArmedState state)
{
base.UpdateHitStateTransforms(state);
switch (state)
{
case ArmedState.Hit:
case ArmedState.Miss:
this.FadeOut(250);
break;
}
}
}
#endregion
}
}

View File

@ -13,19 +13,20 @@ using osu.Game.Screens.Play;
namespace osu.Game.Tests.Visual.Gameplay
{
[Description("Player instantiated with a replay.")]
public class TestSceneReplay : AllPlayersTestScene
public class TestSceneReplay : TestSceneAllRulesetPlayers
{
protected override Player CreatePlayer(Ruleset ruleset)
{
var beatmap = Beatmap.Value.GetPlayableBeatmap(ruleset.RulesetInfo, Array.Empty<Mod>());
return new ScoreAccessibleReplayPlayer(ruleset.GetAutoplayMod().CreateReplayScore(beatmap));
return new ScoreAccessibleReplayPlayer(ruleset.GetAutoplayMod()?.CreateReplayScore(beatmap));
}
protected override void AddCheckSteps()
{
AddUntilStep("score above zero", () => ((ScoreAccessibleReplayPlayer)Player).ScoreProcessor.TotalScore.Value > 0);
AddUntilStep("key counter counted keys", () => ((ScoreAccessibleReplayPlayer)Player).HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 0));
AddAssert("cannot fail", () => !((ScoreAccessibleReplayPlayer)Player).AllowFail);
}
private class ScoreAccessibleReplayPlayer : ReplayPlayer
@ -33,6 +34,8 @@ namespace osu.Game.Tests.Visual.Gameplay
public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
public new HUDOverlay HUDOverlay => base.HUDOverlay;
public bool AllowFail => base.CheckModsAllowFailure();
protected override bool PauseOnFocusLost => false;
public ScoreAccessibleReplayPlayer(Score score)

View File

@ -5,23 +5,19 @@ using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Game.Online;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Rulesets.Osu;
using osu.Game.Scoring;
using osu.Game.Screens.Play;
using osu.Game.Users;
using osuTK;
using System;
using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Game.Rulesets;
using osu.Game.Screens.Ranking;
namespace osu.Game.Tests.Visual.Gameplay
{
[TestFixture]
public class TestSceneReplayDownloadButton : OsuTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(ReplayDownloadButton)
};
[Resolved]
private RulesetStore rulesets { get; set; }
private TestReplayDownloadButton downloadButton;
@ -32,6 +28,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep(@"locally available state", () => downloadButton.SetDownloadState(DownloadState.LocallyAvailable));
AddStep(@"not downloaded state", () => downloadButton.SetDownloadState(DownloadState.NotDownloaded));
createButton(false);
createButtonNoScore();
}
private void createButton(bool withReplay)
@ -42,25 +39,39 @@ namespace osu.Game.Tests.Visual.Gameplay
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(80, 40),
};
});
AddUntilStep("wait for load", () => downloadButton.IsLoaded);
}
private void createButtonNoScore()
{
AddStep("create button with null score", () =>
{
Child = downloadButton = new TestReplayDownloadButton(null)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
};
});
AddUntilStep("wait for load", () => downloadButton.IsLoaded);
}
private ScoreInfo getScoreInfo(bool replayAvailable)
{
return new APILegacyScoreInfo
{
ID = 1,
OnlineScoreID = 2553163309,
Ruleset = new OsuRuleset().RulesetInfo,
OnlineRulesetID = 0,
Replay = replayAvailable,
User = new User
{
Id = 39828,
Username = @"WubWoofWolf",
}
};
}.CreateScoreInfo(rulesets);
}
private class TestReplayDownloadButton : ReplayDownloadButton

View File

@ -0,0 +1,283 @@
// 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.Collections.Generic;
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.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Framework.Input.StateChanges;
using osu.Framework.Testing;
using osu.Framework.Threading;
using osu.Game.Beatmaps;
using osu.Game.Graphics.Sprites;
using osu.Game.Replays;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.Play;
using osu.Game.Tests.Visual.UserInterface;
using osuTK;
using osuTK.Graphics;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Gameplay
{
public class TestSceneReplayRecorder : OsuManualInputManagerTestScene
{
private TestRulesetInputManager playbackManager;
private TestRulesetInputManager recordingManager;
private Replay replay;
private TestReplayRecorder recorder;
[Cached]
private GameplayBeatmap gameplayBeatmap = new GameplayBeatmap(new Beatmap());
[SetUp]
public void SetUp() => Schedule(() =>
{
replay = new Replay();
Add(new GridContainer
{
RelativeSizeAxes = Axes.Both,
Content = new[]
{
new Drawable[]
{
recordingManager = new TestRulesetInputManager(new TestSceneModSettings.TestRulesetInfo(), 0, SimultaneousBindingMode.Unique)
{
Recorder = recorder = new TestReplayRecorder(replay)
{
ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos),
},
Child = new Container
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
new Box
{
Colour = Color4.Brown,
RelativeSizeAxes = Axes.Both,
},
new OsuSpriteText
{
Text = "Recording",
Scale = new Vector2(3),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
new TestInputConsumer()
}
},
}
},
new Drawable[]
{
playbackManager = new TestRulesetInputManager(new TestSceneModSettings.TestRulesetInfo(), 0, SimultaneousBindingMode.Unique)
{
ReplayInputHandler = new TestFramedReplayInputHandler(replay)
{
GamefieldToScreenSpace = pos => playbackManager.ToScreenSpace(pos),
},
Child = new Container
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
new Box
{
Colour = Color4.DarkBlue,
RelativeSizeAxes = Axes.Both,
},
new OsuSpriteText
{
Text = "Playback",
Scale = new Vector2(3),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
new TestInputConsumer()
}
},
}
}
}
});
});
[Test]
public void TestBasic()
{
AddStep("move to center", () => InputManager.MoveMouseTo(recordingManager.ScreenSpaceDrawQuad.Centre));
AddUntilStep("one frame recorded", () => replay.Frames.Count == 1);
AddAssert("position matches", () => playbackManager.ChildrenOfType<Box>().First().Position == recordingManager.ChildrenOfType<Box>().First().Position);
}
[Test]
public void TestHighFrameRate()
{
ScheduledDelegate moveFunction = null;
AddStep("move to center", () => InputManager.MoveMouseTo(recordingManager.ScreenSpaceDrawQuad.Centre));
AddStep("much move", () => moveFunction = Scheduler.AddDelayed(() =>
InputManager.MoveMouseTo(InputManager.CurrentState.Mouse.Position + new Vector2(-1, 0)), 10, true));
AddWaitStep("move", 10);
AddStep("stop move", () => moveFunction.Cancel());
AddAssert("at least 60 frames recorded", () => replay.Frames.Count > 60);
}
[Test]
public void TestLimitedFrameRate()
{
ScheduledDelegate moveFunction = null;
AddStep("lower rate", () => recorder.RecordFrameRate = 2);
AddStep("move to center", () => InputManager.MoveMouseTo(recordingManager.ScreenSpaceDrawQuad.Centre));
AddStep("much move", () => moveFunction = Scheduler.AddDelayed(() =>
InputManager.MoveMouseTo(InputManager.CurrentState.Mouse.Position + new Vector2(-1, 0)), 10, true));
AddWaitStep("move", 10);
AddStep("stop move", () => moveFunction.Cancel());
AddAssert("less than 10 frames recorded", () => replay.Frames.Count < 10);
}
[Test]
public void TestLimitedFrameRateWithImportantFrames()
{
ScheduledDelegate moveFunction = null;
AddStep("lower rate", () => recorder.RecordFrameRate = 2);
AddStep("move to center", () => InputManager.MoveMouseTo(recordingManager.ScreenSpaceDrawQuad.Centre));
AddStep("much move with press", () => moveFunction = Scheduler.AddDelayed(() =>
{
InputManager.MoveMouseTo(InputManager.CurrentState.Mouse.Position + new Vector2(-1, 0));
InputManager.Click(MouseButton.Left);
}, 10, true));
AddWaitStep("move", 10);
AddStep("stop move", () => moveFunction.Cancel());
AddAssert("at least 60 frames recorded", () => replay.Frames.Count > 60);
}
protected override void Update()
{
base.Update();
playbackManager?.ReplayInputHandler.SetFrameFromTime(Time.Current - 100);
}
[TearDownSteps]
public void TearDown()
{
AddStep("stop recorder", () => recorder.Expire());
}
public class TestFramedReplayInputHandler : FramedReplayInputHandler<TestReplayFrame>
{
public TestFramedReplayInputHandler(Replay replay)
: base(replay)
{
}
public override void CollectPendingInputs(List<IInput> inputs)
{
inputs.Add(new MousePositionAbsoluteInput { Position = GamefieldToScreenSpace(CurrentFrame?.Position ?? Vector2.Zero) });
inputs.Add(new ReplayState<TestAction> { PressedActions = CurrentFrame?.Actions ?? new List<TestAction>() });
}
}
public class TestInputConsumer : CompositeDrawable, IKeyBindingHandler<TestAction>
{
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Parent.ReceivePositionalInputAt(screenSpacePos);
private readonly Box box;
public TestInputConsumer()
{
Size = new Vector2(30);
Origin = Anchor.Centre;
InternalChildren = new Drawable[]
{
box = new Box
{
Colour = Color4.Black,
RelativeSizeAxes = Axes.Both,
},
};
}
protected override bool OnMouseMove(MouseMoveEvent e)
{
Position = e.MousePosition;
return base.OnMouseMove(e);
}
public bool OnPressed(TestAction action)
{
box.Colour = Color4.White;
return true;
}
public void OnReleased(TestAction action)
{
box.Colour = Color4.Black;
}
}
public class TestRulesetInputManager : RulesetInputManager<TestAction>
{
public TestRulesetInputManager(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique)
: base(ruleset, variant, unique)
{
}
protected override KeyBindingContainer<TestAction> CreateKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique)
=> new TestKeyBindingContainer();
internal class TestKeyBindingContainer : KeyBindingContainer<TestAction>
{
public override IEnumerable<KeyBinding> DefaultKeyBindings => new[]
{
new KeyBinding(InputKey.MouseLeft, TestAction.Down),
};
}
}
public class TestReplayFrame : ReplayFrame
{
public Vector2 Position;
public List<TestAction> Actions = new List<TestAction>();
public TestReplayFrame(double time, Vector2 position, params TestAction[] actions)
: base(time)
{
Position = position;
Actions.AddRange(actions);
}
}
public enum TestAction
{
Down,
}
internal class TestReplayRecorder : ReplayRecorder<TestAction>
{
public TestReplayRecorder(Replay target)
: base(target)
{
}
protected override ReplayFrame HandleFrame(Vector2 mousePosition, List<TestAction> actions, ReplayFrame previousFrame)
=> new TestReplayFrame(Time.Current, mousePosition, actions.ToArray());
}
}
}

View File

@ -0,0 +1,217 @@
// 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.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Framework.Input.StateChanges;
using osu.Game.Beatmaps;
using osu.Game.Graphics.Sprites;
using osu.Game.Replays;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.Play;
using osu.Game.Tests.Visual.UserInterface;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Tests.Visual.Gameplay
{
public class TestSceneReplayRecording : OsuTestScene
{
private readonly TestRulesetInputManager playbackManager;
private readonly TestRulesetInputManager recordingManager;
[Cached]
private GameplayBeatmap gameplayBeatmap = new GameplayBeatmap(new Beatmap());
public TestSceneReplayRecording()
{
Replay replay = new Replay();
Add(new GridContainer
{
RelativeSizeAxes = Axes.Both,
Content = new[]
{
new Drawable[]
{
recordingManager = new TestRulesetInputManager(new TestSceneModSettings.TestRulesetInfo(), 0, SimultaneousBindingMode.Unique)
{
Recorder = new TestReplayRecorder(replay)
{
ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos)
},
Child = new Container
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
new Box
{
Colour = Color4.Brown,
RelativeSizeAxes = Axes.Both,
},
new OsuSpriteText
{
Text = "Recording",
Scale = new Vector2(3),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
new TestConsumer()
}
},
}
},
new Drawable[]
{
playbackManager = new TestRulesetInputManager(new TestSceneModSettings.TestRulesetInfo(), 0, SimultaneousBindingMode.Unique)
{
ReplayInputHandler = new TestFramedReplayInputHandler(replay)
{
GamefieldToScreenSpace = pos => playbackManager.ToScreenSpace(pos),
},
Child = new Container
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
new Box
{
Colour = Color4.DarkBlue,
RelativeSizeAxes = Axes.Both,
},
new OsuSpriteText
{
Text = "Playback",
Scale = new Vector2(3),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
new TestConsumer()
}
},
}
}
}
});
}
protected override void Update()
{
base.Update();
playbackManager.ReplayInputHandler.SetFrameFromTime(Time.Current - 500);
}
}
public class TestFramedReplayInputHandler : FramedReplayInputHandler<TestReplayFrame>
{
public TestFramedReplayInputHandler(Replay replay)
: base(replay)
{
}
public override void CollectPendingInputs(List<IInput> inputs)
{
inputs.Add(new MousePositionAbsoluteInput { Position = GamefieldToScreenSpace(CurrentFrame?.Position ?? Vector2.Zero) });
inputs.Add(new ReplayState<TestAction> { PressedActions = CurrentFrame?.Actions ?? new List<TestAction>() });
}
}
public class TestConsumer : CompositeDrawable, IKeyBindingHandler<TestAction>
{
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Parent.ReceivePositionalInputAt(screenSpacePos);
private readonly Box box;
public TestConsumer()
{
Size = new Vector2(30);
Origin = Anchor.Centre;
InternalChildren = new Drawable[]
{
box = new Box
{
Colour = Color4.Black,
RelativeSizeAxes = Axes.Both,
},
};
}
protected override bool OnMouseMove(MouseMoveEvent e)
{
Position = e.MousePosition;
return base.OnMouseMove(e);
}
public bool OnPressed(TestAction action)
{
box.Colour = Color4.White;
return true;
}
public void OnReleased(TestAction action)
{
box.Colour = Color4.Black;
}
}
public class TestRulesetInputManager : RulesetInputManager<TestAction>
{
public TestRulesetInputManager(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique)
: base(ruleset, variant, unique)
{
}
protected override KeyBindingContainer<TestAction> CreateKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique)
=> new TestKeyBindingContainer();
internal class TestKeyBindingContainer : KeyBindingContainer<TestAction>
{
public override IEnumerable<KeyBinding> DefaultKeyBindings => new[]
{
new KeyBinding(InputKey.MouseLeft, TestAction.Down),
};
}
}
public class TestReplayFrame : ReplayFrame
{
public Vector2 Position;
public List<TestAction> Actions = new List<TestAction>();
public TestReplayFrame(double time, Vector2 position, params TestAction[] actions)
: base(time)
{
Position = position;
Actions.AddRange(actions);
}
}
public enum TestAction
{
Down,
}
internal class TestReplayRecorder : ReplayRecorder<TestAction>
{
public TestReplayRecorder(Replay target)
: base(target)
{
}
protected override ReplayFrame HandleFrame(Vector2 mousePosition, List<TestAction> actions, ReplayFrame previousFrame) =>
new TestReplayFrame(Time.Current, mousePosition, actions.ToArray());
}
}

View File

@ -3,6 +3,7 @@
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
using osu.Game.Screens.Play.HUD;
using osu.Game.Screens.Play.PlayerSettings;
@ -20,6 +21,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
State = { Value = Visibility.Visible }
});
Add(container = new ExampleContainer());
@ -46,7 +48,10 @@ namespace osu.Game.Tests.Visual.Gameplay
private class ExampleContainer : PlayerSettingsGroup
{
protected override string Title => @"example";
public ExampleContainer()
: base("example")
{
}
}
}
}

View File

@ -1,67 +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.
using System;
using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using osu.Game.Screens.Play;
using osu.Game.Screens.Ranking;
using osu.Game.Screens.Ranking.Pages;
using osu.Game.Users;
namespace osu.Game.Tests.Visual.Gameplay
{
[TestFixture]
public class TestSceneResults : ScreenTestScene
{
private BeatmapManager beatmaps;
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(ScoreInfo),
typeof(Results),
typeof(ResultsPage),
typeof(ScoreResultsPage),
typeof(LocalLeaderboardPage)
};
[BackgroundDependencyLoader]
private void load(BeatmapManager beatmaps)
{
this.beatmaps = beatmaps;
}
protected override void LoadComplete()
{
base.LoadComplete();
var beatmapInfo = beatmaps.QueryBeatmap(b => b.RulesetID == 0);
if (beatmapInfo != null)
Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmapInfo);
LoadScreen(new SoloResults(new ScoreInfo
{
TotalScore = 2845370,
Accuracy = 0.98,
MaxCombo = 123,
Rank = ScoreRank.A,
Date = DateTimeOffset.Now,
Statistics = new Dictionary<HitResult, int>
{
{ HitResult.Great, 50 },
{ HitResult.Good, 20 },
{ HitResult.Meh, 50 },
{ HitResult.Miss, 1 }
},
User = new User
{
Username = "peppy",
}
}));
}
}
}

View File

@ -1,107 +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.
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.MathUtils;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Screens.Play.HUD;
using osuTK;
namespace osu.Game.Tests.Visual.Gameplay
{
[TestFixture]
public class TestSceneScoreCounter : OsuTestScene
{
public TestSceneScoreCounter()
{
int numerator = 0, denominator = 0;
ScoreCounter score = new ScoreCounter(7)
{
Origin = Anchor.TopRight,
Anchor = Anchor.TopRight,
TextSize = 40,
Margin = new MarginPadding(20),
};
Add(score);
ComboCounter comboCounter = new StandardComboCounter
{
Origin = Anchor.BottomLeft,
Anchor = Anchor.BottomLeft,
Margin = new MarginPadding(10),
TextSize = 40,
};
Add(comboCounter);
PercentageCounter accuracyCounter = new PercentageCounter
{
Origin = Anchor.TopRight,
Anchor = Anchor.TopRight,
Position = new Vector2(-20, 60),
};
Add(accuracyCounter);
StarCounter stars = new StarCounter
{
Origin = Anchor.BottomLeft,
Anchor = Anchor.BottomLeft,
Position = new Vector2(20, -160),
CountStars = 5,
};
Add(stars);
SpriteText starsLabel = new OsuSpriteText
{
Origin = Anchor.BottomLeft,
Anchor = Anchor.BottomLeft,
Position = new Vector2(20, -190),
Text = stars.CountStars.ToString("0.00"),
};
Add(starsLabel);
AddStep(@"Reset all", delegate
{
score.Current.Value = 0;
comboCounter.Current.Value = 0;
numerator = denominator = 0;
accuracyCounter.SetFraction(0, 0);
stars.CountStars = 0;
starsLabel.Text = stars.CountStars.ToString("0.00");
});
AddStep(@"Hit! :D", delegate
{
score.Current.Value += 300 + (ulong)(300.0 * (comboCounter.Current.Value > 0 ? comboCounter.Current.Value - 1 : 0) / 25.0);
comboCounter.Increment();
numerator++;
denominator++;
accuracyCounter.SetFraction(numerator, denominator);
});
AddStep(@"miss...", delegate
{
comboCounter.Current.Value = 0;
denominator++;
accuracyCounter.SetFraction(numerator, denominator);
});
AddStep(@"Alter stars", delegate
{
stars.CountStars = RNG.NextSingle() * (stars.StarCount + 1);
starsLabel.Text = stars.CountStars.ToString("0.00");
});
AddStep(@"Stop counters", delegate
{
score.StopRolling();
comboCounter.StopRolling();
accuracyCounter.StopRolling();
stars.StopAnimation();
});
}
}
}

View File

@ -3,37 +3,44 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Threading;
using osu.Framework.Utils;
using osu.Game.Configuration;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Timing;
using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Tests.Visual.Gameplay
{
[TestFixture]
public class TestSceneScrollingHitObjects : OsuTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[] { typeof(Playfield) };
[Cached(typeof(IReadOnlyList<Mod>))]
private IReadOnlyList<Mod> mods { get; set; } = Array.Empty<Mod>();
private const int time_range = 5000;
private const int spawn_rate = time_range / 10;
private readonly ScrollingTestContainer[] scrollContainers = new ScrollingTestContainer[4];
private readonly TestPlayfield[] playfields = new TestPlayfield[4];
private ScheduledDelegate hitObjectSpawnDelegate;
public TestSceneScrollingHitObjects()
[SetUp]
public void Setup() => Schedule(() =>
{
Add(new GridContainer
Child = new GridContainer
{
RelativeSizeAxes = Axes.Both,
Content = new[]
@ -43,48 +50,119 @@ namespace osu.Game.Tests.Visual.Gameplay
scrollContainers[0] = new ScrollingTestContainer(ScrollingDirection.Up)
{
RelativeSizeAxes = Axes.Both,
Child = playfields[0] = new TestPlayfield()
Child = playfields[0] = new TestPlayfield(),
TimeRange = time_range
},
scrollContainers[1] = new ScrollingTestContainer(ScrollingDirection.Up)
scrollContainers[1] = new ScrollingTestContainer(ScrollingDirection.Down)
{
RelativeSizeAxes = Axes.Both,
Child = playfields[1] = new TestPlayfield()
Child = playfields[1] = new TestPlayfield(),
TimeRange = time_range
},
},
new Drawable[]
{
scrollContainers[2] = new ScrollingTestContainer(ScrollingDirection.Up)
scrollContainers[2] = new ScrollingTestContainer(ScrollingDirection.Left)
{
RelativeSizeAxes = Axes.Both,
Child = playfields[2] = new TestPlayfield()
Child = playfields[2] = new TestPlayfield(),
TimeRange = time_range
},
scrollContainers[3] = new ScrollingTestContainer(ScrollingDirection.Up)
scrollContainers[3] = new ScrollingTestContainer(ScrollingDirection.Right)
{
RelativeSizeAxes = Axes.Both,
Child = playfields[3] = new TestPlayfield()
Child = playfields[3] = new TestPlayfield(),
TimeRange = time_range
}
}
}
});
};
AddStep("Constant scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Constant));
AddStep("Overlapping scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Overlapping));
AddStep("Sequential scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Sequential));
hitObjectSpawnDelegate?.Cancel();
});
AddSliderStep("Time range", 100, 10000, 5000, v => scrollContainers.ForEach(c => c.TimeRange = v));
AddStep("Add control point", () => addControlPoint(Time.Current + 5000));
}
protected override void LoadComplete()
private void setUpHitObjects() => AddStep("set up hit objects", () =>
{
base.LoadComplete();
scrollContainers.ForEach(c => c.ControlPoints.Add(new MultiplierControlPoint(0)));
for (int i = 0; i <= 5000; i += 1000)
for (int i = spawn_rate / 2; i <= time_range; i += spawn_rate)
addHitObject(Time.Current + i);
Scheduler.AddDelayed(() => addHitObject(Time.Current + 5000), 1000, true);
hitObjectSpawnDelegate = Scheduler.AddDelayed(() => addHitObject(Time.Current + time_range), spawn_rate, true);
});
private IList<MultiplierControlPoint> testControlPoints => new List<MultiplierControlPoint>
{
new MultiplierControlPoint(time_range) { DifficultyPoint = { SpeedMultiplier = 1.25 } },
new MultiplierControlPoint(1.5 * time_range) { DifficultyPoint = { SpeedMultiplier = 1 } },
new MultiplierControlPoint(2 * time_range) { DifficultyPoint = { SpeedMultiplier = 1.5 } }
};
[Test]
public void TestScrollAlgorithms()
{
setUpHitObjects();
AddStep("constant scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Constant));
AddStep("overlapping scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Overlapping));
AddStep("sequential scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Sequential));
AddSliderStep("time range", 100, 10000, time_range, v => scrollContainers.Where(c => c != null).ForEach(c => c.TimeRange = v));
AddStep("add control points", () => addControlPoints(testControlPoints, Time.Current));
}
[Test]
public void TestConstantScrollLifetime()
{
setUpHitObjects();
AddStep("set constant scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Constant));
// scroll container time range must be less than the rate of spawning hitobjects
// otherwise the hitobjects will spawn already partly visible on screen and look wrong
AddStep("set time range", () => scrollContainers.ForEach(c => c.TimeRange = time_range / 2.0));
}
[Test]
public void TestSequentialScrollLifetime()
{
setUpHitObjects();
AddStep("set sequential scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Sequential));
AddStep("set time range", () => scrollContainers.ForEach(c => c.TimeRange = time_range / 2.0));
AddStep("add control points", () => addControlPoints(testControlPoints, Time.Current));
}
[Test]
public void TestSlowSequentialScroll()
{
AddStep("set sequential scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Sequential));
AddStep("set time range", () => scrollContainers.ForEach(c => c.TimeRange = time_range));
AddStep("add control points", () => addControlPoints(
new List<MultiplierControlPoint>
{
new MultiplierControlPoint { Velocity = 0.1 }
},
Time.Current + time_range));
// All of the hit objects added below should be immediately visible on screen
AddStep("add hit objects", () =>
{
for (int i = 0; i < 20; ++i)
{
addHitObject(Time.Current + time_range * (2 + 0.1 * i));
}
});
}
[Test]
public void TestOverlappingScrollLifetime()
{
setUpHitObjects();
AddStep("set overlapping scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Overlapping));
AddStep("set time range", () => scrollContainers.ForEach(c => c.TimeRange = time_range / 2.0));
AddStep("add control points", () => addControlPoints(testControlPoints, Time.Current));
}
private void addHitObject(double time)
@ -98,28 +176,27 @@ namespace osu.Game.Tests.Visual.Gameplay
});
}
private void addControlPoint(double time)
private TestDrawableControlPoint createDrawablePoint(TestPlayfield playfield, double t)
{
scrollContainers.ForEach(c =>
var obj = new TestDrawableControlPoint(playfield.Direction, t);
setAnchor(obj, playfield);
return obj;
}
private void addControlPoints(IList<MultiplierControlPoint> controlPoints, double sequenceStartTime)
{
controlPoints.ForEach(point => point.StartTime += sequenceStartTime);
scrollContainers.ForEach(container =>
{
c.ControlPoints.Add(new MultiplierControlPoint(time) { DifficultyPoint = { SpeedMultiplier = 3 } });
c.ControlPoints.Add(new MultiplierControlPoint(time + 2000) { DifficultyPoint = { SpeedMultiplier = 2 } });
c.ControlPoints.Add(new MultiplierControlPoint(time + 3000) { DifficultyPoint = { SpeedMultiplier = 1 } });
container.ControlPoints.AddRange(controlPoints);
});
playfields.ForEach(p =>
foreach (var playfield in playfields)
{
TestDrawableControlPoint createDrawablePoint(double t)
{
var obj = new TestDrawableControlPoint(p.Direction, t);
setAnchor(obj, p);
return obj;
}
p.Add(createDrawablePoint(time));
p.Add(createDrawablePoint(time + 2000));
p.Add(createDrawablePoint(time + 3000));
});
foreach (var controlPoint in controlPoints)
playfield.Add(createDrawablePoint(playfield, controlPoint.StartTime));
}
}
private void setAnchor(DrawableHitObject obj, TestPlayfield playfield)
@ -174,7 +251,7 @@ namespace osu.Game.Tests.Visual.Gameplay
private class TestDrawableControlPoint : DrawableHitObject<HitObject>
{
public TestDrawableControlPoint(ScrollingDirection direction, double time)
: base(new HitObject { StartTime = time })
: base(new HitObject { StartTime = time, HitWindows = HitWindows.Empty })
{
Origin = Anchor.Centre;
@ -205,12 +282,18 @@ namespace osu.Game.Tests.Visual.Gameplay
private class TestDrawableHitObject : DrawableHitObject<HitObject>
{
public TestDrawableHitObject(double time)
: base(new HitObject { StartTime = time })
: base(new HitObject { StartTime = time, HitWindows = HitWindows.Empty })
{
Origin = Anchor.Centre;
Origin = Anchor.Custom;
OriginPosition = new Vector2(75 / 4.0f);
AutoSizeAxes = Axes.Both;
AddInternal(new Box { Size = new Vector2(75) });
AddInternal(new Box
{
Size = new Vector2(75),
Colour = new Color4(RNG.NextSingle(), RNG.NextSingle(), RNG.NextSingle(), 1)
});
}
}
}

View File

@ -0,0 +1,49 @@
// 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.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Testing;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Play.HUD;
namespace osu.Game.Tests.Visual.Gameplay
{
public class TestSceneSkinnableAccuracyCounter : SkinnableTestScene
{
private IEnumerable<SkinnableAccuracyCounter> accuracyCounters => CreatedDrawables.OfType<SkinnableAccuracyCounter>();
protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset();
[SetUpSteps]
public void SetUpSteps()
{
AddStep("Create combo counters", () => SetContents(() =>
{
var accuracyCounter = new SkinnableAccuracyCounter();
accuracyCounter.Current.Value = 1;
return accuracyCounter;
}));
}
[Test]
public void TestChangingAccuracy()
{
AddStep(@"Reset all", delegate
{
foreach (var s in accuracyCounters)
s.Current.Value = 1;
});
AddStep(@"Hit! :D", delegate
{
foreach (var s in accuracyCounters)
s.Current.Value -= 0.023f;
});
}
}
}

View File

@ -10,6 +10,7 @@ using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Textures;
using osu.Game.Audio;
@ -43,16 +44,15 @@ namespace osu.Game.Tests.Visual.Gameplay
{
new ExposedSkinnableDrawable("default", _ => new DefaultBox(), _ => true),
new ExposedSkinnableDrawable("available", _ => new DefaultBox(), _ => true),
new ExposedSkinnableDrawable("available", _ => new DefaultBox(), _ => true, ConfineMode.ScaleToFit),
new ExposedSkinnableDrawable("available", _ => new DefaultBox(), _ => true, ConfineMode.NoScaling)
}
},
};
});
AddAssert("check sizes", () => fill.Children.Select(c => c.Drawable.DrawWidth).SequenceEqual(new float[] { 30, 30, 30, 50 }));
AddAssert("check sizes", () => fill.Children.Select(c => c.Drawable.DrawWidth).SequenceEqual(new float[] { 30, 30, 50 }));
AddStep("adjust scale", () => fill.Scale = new Vector2(2));
AddAssert("check sizes unchanged by scale", () => fill.Children.Select(c => c.Drawable.DrawWidth).SequenceEqual(new float[] { 30, 30, 30, 50 }));
AddAssert("check sizes unchanged by scale", () => fill.Children.Select(c => c.Drawable.DrawWidth).SequenceEqual(new float[] { 30, 30, 50 }));
}
[Test]
@ -74,7 +74,6 @@ namespace osu.Game.Tests.Visual.Gameplay
Children = new[]
{
new ExposedSkinnableDrawable("default", _ => new DefaultBox(), _ => true),
new ExposedSkinnableDrawable("available", _ => new DefaultBox(), _ => true),
new ExposedSkinnableDrawable("available", _ => new DefaultBox(), _ => true, ConfineMode.ScaleToFit),
new ExposedSkinnableDrawable("available", _ => new DefaultBox(), _ => true, ConfineMode.NoScaling)
}
@ -82,9 +81,9 @@ namespace osu.Game.Tests.Visual.Gameplay
};
});
AddAssert("check sizes", () => fill.Children.Select(c => c.Drawable.DrawWidth).SequenceEqual(new float[] { 50, 30, 50, 30 }));
AddAssert("check sizes", () => fill.Children.Select(c => c.Drawable.DrawWidth).SequenceEqual(new float[] { 50, 50, 30 }));
AddStep("adjust scale", () => fill.Scale = new Vector2(2));
AddAssert("check sizes unchanged by scale", () => fill.Children.Select(c => c.Drawable.DrawWidth).SequenceEqual(new float[] { 50, 30, 50, 30 }));
AddAssert("check sizes unchanged by scale", () => fill.Children.Select(c => c.Drawable.DrawWidth).SequenceEqual(new float[] { 50, 50, 30 }));
}
[Test]
@ -182,7 +181,7 @@ namespace osu.Game.Tests.Visual.Gameplay
public new Drawable Drawable => base.Drawable;
public ExposedSkinnableDrawable(string name, Func<ISkinComponent, Drawable> defaultImplementation, Func<ISkinSource, bool> allowFallback = null,
ConfineMode confineMode = ConfineMode.ScaleDownToFit)
ConfineMode confineMode = ConfineMode.ScaleToFit)
: base(new TestSkinComponent(name), defaultImplementation, allowFallback, confineMode)
{
}
@ -297,7 +296,7 @@ namespace osu.Game.Tests.Visual.Gameplay
}
: null;
public Texture GetTexture(string componentName) => throw new NotImplementedException();
public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => throw new NotImplementedException();
public SampleChannel GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
@ -308,7 +307,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{
public Drawable GetDrawableComponent(ISkinComponent componentName) => new SecondarySourceBox();
public Texture GetTexture(string componentName) => throw new NotImplementedException();
public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => throw new NotImplementedException();
public SampleChannel GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
@ -320,27 +319,29 @@ namespace osu.Game.Tests.Visual.Gameplay
{
public Drawable GetDrawableComponent(ISkinComponent componentName) => new BaseSourceBox();
public Texture GetTexture(string componentName) => throw new NotImplementedException();
public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => throw new NotImplementedException();
public SampleChannel GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => throw new NotImplementedException();
public event Action SourceChanged;
public event Action SourceChanged
{
add { }
remove { }
}
}
private class TestSkinComponent : ISkinComponent
{
private readonly string name;
public TestSkinComponent(string name)
{
this.name = name;
LookupName = name;
}
public string ComponentGroup => string.Empty;
public string LookupName => name;
public string LookupName { get; }
}
}
}

View File

@ -0,0 +1,99 @@
// 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.Allocation;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Game.Configuration;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Play;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Gameplay
{
public class TestSceneSkinnableHUDOverlay : SkinnableTestScene
{
private HUDOverlay hudOverlay;
private IEnumerable<HUDOverlay> hudOverlays => CreatedDrawables.OfType<HUDOverlay>();
// best way to check without exposing.
private Drawable hideTarget => hudOverlay.KeyCounter;
private FillFlowContainer<KeyCounter> keyCounterFlow => hudOverlay.KeyCounter.ChildrenOfType<FillFlowContainer<KeyCounter>>().First();
[Resolved]
private OsuConfigManager config { get; set; }
[Test]
public void TestComboCounterIncrementing()
{
createNew();
AddRepeatStep("increase combo", () =>
{
foreach (var hud in hudOverlays)
hud.ComboCounter.Current.Value++;
}, 10);
AddStep("reset combo", () =>
{
foreach (var hud in hudOverlays)
hud.ComboCounter.Current.Value = 0;
});
}
[Test]
public void TestFadesInOnLoadComplete()
{
float? initialAlpha = null;
createNew(h => h.OnLoadComplete += _ => initialAlpha = hideTarget.Alpha);
AddUntilStep("wait for load", () => hudOverlay.IsAlive);
AddAssert("initial alpha was less than 1", () => initialAlpha < 1);
}
[Test]
public void TestHideExternally()
{
createNew();
AddStep("set showhud false", () => hudOverlays.ForEach(h => h.ShowHud.Value = false));
AddUntilStep("hidetarget is hidden", () => !hideTarget.IsPresent);
AddAssert("pause button is still visible", () => hudOverlay.HoldToQuit.IsPresent);
// Key counter flow container should not be affected by this, only the key counter display will be hidden as checked above.
AddAssert("key counter flow not affected", () => keyCounterFlow.IsPresent);
}
private void createNew(Action<HUDOverlay> action = null)
{
AddStep("create overlay", () =>
{
SetContents(() =>
{
hudOverlay = new HUDOverlay(null, null, null, Array.Empty<Mod>());
// Add any key just to display the key counter visually.
hudOverlay.KeyCounter.Add(new KeyCounterKeyboard(Key.Space));
hudOverlay.ComboCounter.Current.Value = 1;
action?.Invoke(hudOverlay);
return hudOverlay;
});
});
}
protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset();
}
}

View File

@ -0,0 +1,61 @@
// 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.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Testing;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Judgements;
using osu.Game.Screens.Play;
namespace osu.Game.Tests.Visual.Gameplay
{
public class TestSceneSkinnableHealthDisplay : SkinnableTestScene
{
private IEnumerable<SkinnableHealthDisplay> healthDisplays => CreatedDrawables.OfType<SkinnableHealthDisplay>();
protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset();
[SetUpSteps]
public void SetUpSteps()
{
AddStep("Create health displays", () =>
{
SetContents(() => new SkinnableHealthDisplay());
});
AddStep(@"Reset all", delegate
{
foreach (var s in healthDisplays)
s.Current.Value = 1;
});
}
[Test]
public void TestHealthDisplayIncrementing()
{
AddRepeatStep(@"decrease hp", delegate
{
foreach (var healthDisplay in healthDisplays)
healthDisplay.Current.Value -= 0.08f;
}, 10);
AddRepeatStep(@"increase hp without flash", delegate
{
foreach (var healthDisplay in healthDisplays)
healthDisplay.Current.Value += 0.1f;
}, 3);
AddRepeatStep(@"increase hp with flash", delegate
{
foreach (var healthDisplay in healthDisplays)
{
healthDisplay.Current.Value += 0.1f;
healthDisplay.Flash(new JudgementResult(null, new OsuJudgement()));
}
}, 3);
}
}
}

View File

@ -0,0 +1,54 @@
// 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.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Testing;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Play.HUD;
namespace osu.Game.Tests.Visual.Gameplay
{
public class TestSceneSkinnableScoreCounter : SkinnableTestScene
{
private IEnumerable<SkinnableScoreCounter> scoreCounters => CreatedDrawables.OfType<SkinnableScoreCounter>();
protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset();
[SetUpSteps]
public void SetUpSteps()
{
AddStep("Create combo counters", () => SetContents(() =>
{
var comboCounter = new SkinnableScoreCounter();
comboCounter.Current.Value = 1;
return comboCounter;
}));
}
[Test]
public void TestScoreCounterIncrementing()
{
AddStep(@"Reset all", delegate
{
foreach (var s in scoreCounters)
s.Current.Value = 0;
});
AddStep(@"Hit! :D", delegate
{
foreach (var s in scoreCounters)
s.Current.Value += 300;
});
}
[Test]
public void TestVeryLargeScore()
{
AddStep("set large score", () => scoreCounters.ForEach(counter => counter.Current.Value = 1_000_000_000));
}
}
}

View File

@ -0,0 +1,167 @@
// 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.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Audio;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Textures;
using osu.Framework.Testing;
using osu.Game.Audio;
using osu.Game.Screens.Play;
using osu.Game.Skinning;
namespace osu.Game.Tests.Visual.Gameplay
{
public class TestSceneSkinnableSound : OsuTestScene
{
private TestSkinSourceContainer skinSource;
private PausableSkinnableSound skinnableSound;
[SetUp]
public void SetUpSteps()
{
AddStep("setup hierarchy", () =>
{
Children = new Drawable[]
{
skinSource = new TestSkinSourceContainer
{
RelativeSizeAxes = Axes.Both,
Child = skinnableSound = new PausableSkinnableSound(new SampleInfo("Gameplay/normal-sliderslide"))
},
};
});
}
[Test]
public void TestStoppedSoundDoesntResumeAfterPause()
{
DrawableSample sample = null;
AddStep("start sample with looping", () =>
{
sample = skinnableSound.ChildrenOfType<DrawableSample>().First();
skinnableSound.Looping = true;
skinnableSound.Play();
});
AddUntilStep("wait for sample to start playing", () => sample.Playing);
AddStep("stop sample", () => skinnableSound.Stop());
AddUntilStep("wait for sample to stop playing", () => !sample.Playing);
AddStep("disable sample playback", () => skinSource.SamplePlaybackDisabled.Value = true);
AddStep("enable sample playback", () => skinSource.SamplePlaybackDisabled.Value = false);
AddWaitStep("wait a bit", 5);
AddAssert("sample not playing", () => !sample.Playing);
}
[Test]
public void TestLoopingSoundResumesAfterPause()
{
DrawableSample sample = null;
AddStep("start sample with looping", () =>
{
skinnableSound.Looping = true;
skinnableSound.Play();
sample = skinnableSound.ChildrenOfType<DrawableSample>().First();
});
AddUntilStep("wait for sample to start playing", () => sample.Playing);
AddStep("disable sample playback", () => skinSource.SamplePlaybackDisabled.Value = true);
AddUntilStep("wait for sample to stop playing", () => !sample.Playing);
AddStep("enable sample playback", () => skinSource.SamplePlaybackDisabled.Value = false);
AddUntilStep("wait for sample to start playing", () => sample.Playing);
}
[Test]
public void TestNonLoopingStopsWithPause()
{
DrawableSample sample = null;
AddStep("start sample", () =>
{
skinnableSound.Play();
sample = skinnableSound.ChildrenOfType<DrawableSample>().First();
});
AddAssert("sample playing", () => sample.Playing);
AddStep("disable sample playback", () => skinSource.SamplePlaybackDisabled.Value = true);
AddUntilStep("sample not playing", () => !sample.Playing);
AddStep("enable sample playback", () => skinSource.SamplePlaybackDisabled.Value = false);
AddAssert("sample not playing", () => !sample.Playing);
AddAssert("sample not playing", () => !sample.Playing);
AddAssert("sample not playing", () => !sample.Playing);
}
[Test]
public void TestSkinChangeDoesntPlayOnPause()
{
DrawableSample sample = null;
AddStep("start sample", () =>
{
skinnableSound.Play();
sample = skinnableSound.ChildrenOfType<DrawableSample>().Single();
});
AddAssert("sample playing", () => sample.Playing);
AddStep("disable sample playback", () => skinSource.SamplePlaybackDisabled.Value = true);
AddUntilStep("wait for sample to stop playing", () => !sample.Playing);
AddStep("trigger skin change", () => skinSource.TriggerSourceChanged());
AddAssert("retrieve and ensure current sample is different", () =>
{
DrawableSample oldSample = sample;
sample = skinnableSound.ChildrenOfType<DrawableSample>().Single();
return sample != oldSample;
});
AddAssert("new sample stopped", () => !sample.Playing);
AddStep("enable sample playback", () => skinSource.SamplePlaybackDisabled.Value = false);
AddWaitStep("wait a bit", 5);
AddAssert("new sample not played", () => !sample.Playing);
}
[Cached(typeof(ISkinSource))]
[Cached(typeof(ISamplePlaybackDisabler))]
private class TestSkinSourceContainer : Container, ISkinSource, ISamplePlaybackDisabler
{
[Resolved]
private ISkinSource source { get; set; }
public event Action SourceChanged;
public Bindable<bool> SamplePlaybackDisabled { get; } = new Bindable<bool>();
IBindable<bool> ISamplePlaybackDisabler.SamplePlaybackDisabled => SamplePlaybackDisabled;
public Drawable GetDrawableComponent(ISkinComponent component) => source?.GetDrawableComponent(component);
public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => source?.GetTexture(componentName, wrapModeS, wrapModeT);
public SampleChannel GetSample(ISampleInfo sampleInfo) => source?.GetSample(sampleInfo);
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => source?.GetConfig<TLookup, TValue>(lookup);
public void TriggerSourceChanged()
{
SourceChanged?.Invoke();
}
}
}
}

View File

@ -1,11 +1,10 @@
// 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.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Timing;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Play;
using osuTK;
using osuTK.Input;
@ -13,49 +12,64 @@ using osuTK.Input;
namespace osu.Game.Tests.Visual.Gameplay
{
[TestFixture]
public class TestSceneSkipOverlay : ManualInputManagerTestScene
public class TestSceneSkipOverlay : OsuManualInputManagerTestScene
{
private SkipOverlay skip;
private TestSkipOverlay skip;
private int requestCount;
private double increment;
private GameplayClockContainer gameplayClockContainer;
private GameplayClock gameplayClock;
private const double skip_time = 6000;
[SetUp]
public void SetUp() => Schedule(() =>
{
requestCount = 0;
Child = new Container
increment = skip_time;
var working = CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo));
working.LoadTrack();
Child = gameplayClockContainer = new GameplayClockContainer(working, 0)
{
RelativeSizeAxes = Axes.Both,
Clock = new FramedOffsetClock(Clock)
{
Offset = -Clock.CurrentTime,
},
Children = new Drawable[]
{
skip = new SkipOverlay(6000)
skip = new TestSkipOverlay(skip_time)
{
RequestSeek = _ => requestCount++
RequestSkip = () =>
{
requestCount++;
gameplayClockContainer.Seek(gameplayClock.CurrentTime + increment);
}
}
},
};
gameplayClockContainer.Start();
gameplayClock = gameplayClockContainer.GameplayClock;
});
[Test]
public void TestFadeOnIdle()
{
AddStep("move mouse", () => InputManager.MoveMouseTo(Vector2.Zero));
AddUntilStep("fully visible", () => skip.Children.First().Alpha == 1);
AddUntilStep("wait for fade", () => skip.Children.First().Alpha < 1);
AddUntilStep("fully visible", () => skip.FadingContent.Alpha == 1);
AddUntilStep("wait for fade", () => skip.FadingContent.Alpha < 1);
AddStep("move mouse", () => InputManager.MoveMouseTo(skip.ScreenSpaceDrawQuad.Centre));
AddUntilStep("fully visible", () => skip.Children.First().Alpha == 1);
AddUntilStep("wait for fade", () => skip.Children.First().Alpha < 1);
AddUntilStep("fully visible", () => skip.FadingContent.Alpha == 1);
AddUntilStep("wait for fade", () => skip.FadingContent.Alpha < 1);
}
[Test]
public void TestClickableAfterFade()
{
AddStep("move mouse", () => InputManager.MoveMouseTo(skip.ScreenSpaceDrawQuad.Centre));
AddUntilStep("wait for fade", () => skip.Children.First().Alpha == 0);
AddUntilStep("wait for fade", () => skip.FadingContent.Alpha == 0);
AddStep("click", () => InputManager.Click(MouseButton.Left));
checkRequestCount(1);
}
@ -64,25 +78,53 @@ namespace osu.Game.Tests.Visual.Gameplay
public void TestClickOnlyActuatesOnce()
{
AddStep("move mouse", () => InputManager.MoveMouseTo(skip.ScreenSpaceDrawQuad.Centre));
AddStep("click", () => InputManager.Click(MouseButton.Left));
AddStep("click", () =>
{
increment = skip_time - gameplayClock.CurrentTime - GameplayClockContainer.MINIMUM_SKIP_TIME / 2;
InputManager.Click(MouseButton.Left);
});
AddStep("click", () => InputManager.Click(MouseButton.Left));
AddStep("click", () => InputManager.Click(MouseButton.Left));
AddStep("click", () => InputManager.Click(MouseButton.Left));
checkRequestCount(1);
}
[Test]
public void TestClickOnlyActuatesMultipleTimes()
{
AddStep("set increment lower", () => increment = 3000);
AddStep("move mouse", () => InputManager.MoveMouseTo(skip.ScreenSpaceDrawQuad.Centre));
AddStep("click", () => InputManager.Click(MouseButton.Left));
AddStep("click", () => InputManager.Click(MouseButton.Left));
AddStep("click", () => InputManager.Click(MouseButton.Left));
AddStep("click", () => InputManager.Click(MouseButton.Left));
checkRequestCount(2);
}
[Test]
public void TestDoesntFadeOnMouseDown()
{
AddStep("move mouse", () => InputManager.MoveMouseTo(skip.ScreenSpaceDrawQuad.Centre));
AddStep("button down", () => InputManager.PressButton(MouseButton.Left));
AddUntilStep("wait for overlay disapper", () => !skip.IsAlive);
AddAssert("ensure button didn't disappear", () => skip.Children.First().Alpha > 0);
AddUntilStep("wait for overlay disappear", () => !skip.OverlayContent.IsPresent);
AddAssert("ensure button didn't disappear", () => skip.FadingContent.Alpha > 0);
AddStep("button up", () => InputManager.ReleaseButton(MouseButton.Left));
checkRequestCount(0);
}
private void checkRequestCount(int expected) =>
AddAssert($"request count is {expected}", () => requestCount == expected);
private class TestSkipOverlay : SkipOverlay
{
public TestSkipOverlay(double startTime)
: base(startTime)
{
}
public Drawable OverlayContent => InternalChild;
public Drawable FadingContent => (OverlayContent as Container)?.Child;
}
}
}

View File

@ -0,0 +1,193 @@
// 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.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Lines;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osuTK;
namespace osu.Game.Tests.Visual.Gameplay
{
public class TestSceneSliderPath : OsuTestScene
{
private readonly SmoothPath drawablePath;
private SliderPath path;
public TestSceneSliderPath()
{
Child = drawablePath = new SmoothPath
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre
};
}
[SetUp]
public void Setup() => Schedule(() =>
{
path = new SliderPath();
});
protected override void Update()
{
base.Update();
if (path != null)
{
List<Vector2> vertices = new List<Vector2>();
path.GetPathToProgress(vertices, 0, 1);
drawablePath.Vertices = vertices;
}
}
[Test]
public void TestEmptyPath()
{
}
[TestCase(PathType.Linear)]
[TestCase(PathType.Bezier)]
[TestCase(PathType.Catmull)]
[TestCase(PathType.PerfectCurve)]
public void TestSingleSegment(PathType type)
=> AddStep("create path", () => path.ControlPoints.AddRange(createSegment(type, Vector2.Zero, new Vector2(0, 100), new Vector2(100))));
[TestCase(PathType.Linear)]
[TestCase(PathType.Bezier)]
[TestCase(PathType.Catmull)]
[TestCase(PathType.PerfectCurve)]
public void TestMultipleSegment(PathType type)
{
AddStep("create path", () =>
{
path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero));
path.ControlPoints.AddRange(createSegment(type, new Vector2(0, 100), new Vector2(100), Vector2.Zero));
});
}
[Test]
public void TestAddControlPoint()
{
AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100))));
AddStep("add point", () => path.ControlPoints.Add(new PathControlPoint { Position = { Value = new Vector2(100) } }));
}
[Test]
public void TestInsertControlPoint()
{
AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(100))));
AddStep("insert point", () => path.ControlPoints.Insert(1, new PathControlPoint { Position = { Value = new Vector2(0, 100) } }));
}
[Test]
public void TestRemoveControlPoint()
{
AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100))));
AddStep("remove second point", () => path.ControlPoints.RemoveAt(1));
}
[Test]
public void TestChangePathType()
{
AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100))));
AddStep("change type to bezier", () => path.ControlPoints[0].Type.Value = PathType.Bezier);
}
[Test]
public void TestAddSegmentByChangingType()
{
AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100), new Vector2(100, 0))));
AddStep("change second point type to bezier", () => path.ControlPoints[1].Type.Value = PathType.Bezier);
}
[Test]
public void TestRemoveSegmentByChangingType()
{
AddStep("create path", () =>
{
path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100), new Vector2(100, 0)));
path.ControlPoints[1].Type.Value = PathType.Bezier;
});
AddStep("change second point type to null", () => path.ControlPoints[1].Type.Value = null);
}
[Test]
public void TestRemoveSegmentByRemovingControlPoint()
{
AddStep("create path", () =>
{
path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100), new Vector2(100, 0)));
path.ControlPoints[1].Type.Value = PathType.Bezier;
});
AddStep("remove second point", () => path.ControlPoints.RemoveAt(1));
}
[TestCase(2)]
[TestCase(4)]
public void TestPerfectCurveFallbackScenarios(int points)
{
AddStep("create path", () =>
{
switch (points)
{
case 2:
path.ControlPoints.AddRange(createSegment(PathType.PerfectCurve, Vector2.Zero, new Vector2(0, 100)));
break;
case 4:
path.ControlPoints.AddRange(createSegment(PathType.PerfectCurve, Vector2.Zero, new Vector2(0, 100), new Vector2(100), new Vector2(100, 0)));
break;
}
});
}
[Test]
public void TestLengthenLastSegment()
{
AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100))));
AddStep("lengthen last segment", () => path.ExpectedDistance.Value = 300);
}
[Test]
public void TestShortenLastSegment()
{
AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100))));
AddStep("shorten last segment", () => path.ExpectedDistance.Value = 150);
}
[Test]
public void TestShortenFirstSegment()
{
AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100))));
AddStep("shorten first segment", () => path.ExpectedDistance.Value = 50);
}
[Test]
public void TestShortenToZeroLength()
{
AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100))));
AddStep("shorten to 0 length", () => path.ExpectedDistance.Value = 0);
}
[Test]
public void TestShortenToNegativeLength()
{
AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100))));
AddStep("shorten to -10 length", () => path.ExpectedDistance.Value = -10);
}
private List<PathControlPoint> createSegment(PathType type, params Vector2[] controlPoints)
{
var points = controlPoints.Select(p => new PathControlPoint { Position = { Value = p } }).ToList();
points[0].Type.Value = type;
return points;
}
}
}

View File

@ -5,8 +5,12 @@ using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.MathUtils;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Framework.Timing;
using osu.Game.Graphics;
using osu.Game.Rulesets.Objects;
using osu.Game.Screens.Play;
@ -15,63 +19,120 @@ namespace osu.Game.Tests.Visual.Gameplay
[TestFixture]
public class TestSceneSongProgress : OsuTestScene
{
private readonly SongProgress progress;
private readonly TestSongProgressGraph graph;
private SongProgress progress;
private TestSongProgressGraph graph;
private readonly Container progressContainer;
private readonly StopwatchClock clock;
private readonly FramedClock framedClock;
[Cached]
private readonly GameplayClock gameplayClock;
private readonly FramedClock framedClock;
public TestSceneSongProgress()
{
clock = new StopwatchClock(true);
clock = new StopwatchClock();
gameplayClock = new GameplayClock(framedClock = new FramedClock(clock));
Add(progress = new SongProgress
Add(progressContainer = new Container
{
RelativeSizeAxes = Axes.X,
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
Height = 100,
Y = -100,
Child = new Box
{
RelativeSizeAxes = Axes.Both,
Colour = OsuColour.Gray(1),
}
});
Add(graph = new TestSongProgressGraph
{
RelativeSizeAxes = Axes.X,
Height = 200,
Anchor = Anchor.TopLeft,
Origin = Anchor.TopLeft,
});
AddWaitStep("wait some", 5);
AddAssert("ensure not created", () => graph.CreationCount == 0);
AddStep("display values", displayNewValues);
AddWaitStep("wait some", 5);
AddUntilStep("wait for creation count", () => graph.CreationCount == 1);
AddStep("Toggle Bar", () => progress.AllowSeeking = !progress.AllowSeeking);
AddWaitStep("wait some", 5);
AddUntilStep("wait for creation count", () => graph.CreationCount == 1);
AddStep("Toggle Bar", () => progress.AllowSeeking = !progress.AllowSeeking);
AddWaitStep("wait some", 5);
AddUntilStep("wait for creation count", () => graph.CreationCount == 1);
AddRepeatStep("New Values", displayNewValues, 5);
AddWaitStep("wait some", 5);
AddAssert("ensure debounced", () => graph.CreationCount == 2);
}
private void displayNewValues()
[SetUpSteps]
public void SetupSteps()
{
List<HitObject> objects = new List<HitObject>();
AddStep("add new song progress", () =>
{
if (progress != null)
{
progress.Expire();
progress = null;
}
progressContainer.Add(progress = new SongProgress
{
RelativeSizeAxes = Axes.X,
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
});
});
AddStep("add new big graph", () =>
{
if (graph != null)
{
graph.Expire();
graph = null;
}
Add(graph = new TestSongProgressGraph
{
RelativeSizeAxes = Axes.X,
Height = 200,
Anchor = Anchor.TopLeft,
Origin = Anchor.TopLeft,
});
});
AddStep("reset clock", clock.Reset);
}
[Test]
public void TestGraphRecreation()
{
AddAssert("ensure not created", () => graph.CreationCount == 0);
AddStep("display values", displayRandomValues);
AddUntilStep("wait for creation count", () => graph.CreationCount == 1);
AddRepeatStep("new values", displayRandomValues, 5);
AddWaitStep("wait some", 5);
AddAssert("ensure recreation debounced", () => graph.CreationCount == 2);
}
[Test]
public void TestDisplay()
{
AddStep("display max values", displayMaxValues);
AddUntilStep("wait for graph", () => graph.CreationCount == 1);
AddStep("start", clock.Start);
AddStep("allow seeking", () => progress.AllowSeeking.Value = true);
AddStep("hide graph", () => progress.ShowGraph.Value = false);
AddStep("disallow seeking", () => progress.AllowSeeking.Value = false);
AddStep("allow seeking", () => progress.AllowSeeking.Value = true);
AddStep("show graph", () => progress.ShowGraph.Value = true);
AddStep("stop", clock.Stop);
}
private void displayRandomValues()
{
var objects = new List<HitObject>();
for (double i = 0; i < 5000; i += RNG.NextDouble() * 10 + i / 1000)
objects.Add(new HitObject { StartTime = i });
replaceObjects(objects);
}
private void displayMaxValues()
{
var objects = new List<HitObject>();
for (double i = 0; i < 5000; i++)
objects.Add(new HitObject { StartTime = i });
replaceObjects(objects);
}
private void replaceObjects(List<HitObject> objects)
{
progress.Objects = objects;
graph.Objects = objects;

View File

@ -0,0 +1,304 @@
// 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.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Online.Spectator;
using osu.Game.Replays.Legacy;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Replays;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.Play;
using osu.Game.Tests.Beatmaps.IO;
using osu.Game.Users;
namespace osu.Game.Tests.Visual.Gameplay
{
public class TestSceneSpectator : ScreenTestScene
{
[Cached(typeof(SpectatorStreamingClient))]
private TestSpectatorStreamingClient testSpectatorStreamingClient = new TestSpectatorStreamingClient();
// used just to show beatmap card for the time being.
protected override bool UseOnlineAPI => true;
private Spectator spectatorScreen;
[Resolved]
private OsuGameBase game { get; set; }
private int nextFrame;
private BeatmapSetInfo importedBeatmap;
private int importedBeatmapId;
public override void SetUpSteps()
{
base.SetUpSteps();
AddStep("reset sent frames", () => nextFrame = 0);
AddStep("import beatmap", () =>
{
importedBeatmap = ImportBeatmapTest.LoadOszIntoOsu(game, virtualTrack: true).Result;
importedBeatmapId = importedBeatmap.Beatmaps.First(b => b.RulesetID == 0).OnlineBeatmapID ?? -1;
});
AddStep("add streaming client", () =>
{
Remove(testSpectatorStreamingClient);
Add(testSpectatorStreamingClient);
});
finish();
}
[Test]
public void TestFrameStarvationAndResume()
{
loadSpectatingScreen();
AddAssert("screen hasn't changed", () => Stack.CurrentScreen is Spectator);
start();
sendFrames();
waitForPlayer();
AddAssert("ensure frames arrived", () => replayHandler.HasFrames);
AddUntilStep("wait for frame starvation", () => replayHandler.NextFrame == null);
checkPaused(true);
double? pausedTime = null;
AddStep("store time", () => pausedTime = currentFrameStableTime);
sendFrames();
AddUntilStep("wait for frame starvation", () => replayHandler.NextFrame == null);
checkPaused(true);
AddAssert("time advanced", () => currentFrameStableTime > pausedTime);
}
[Test]
public void TestPlayStartsWithNoFrames()
{
loadSpectatingScreen();
start();
waitForPlayer();
checkPaused(true);
sendFrames(1000); // send enough frames to ensure play won't be paused
checkPaused(false);
}
[Test]
public void TestSpectatingDuringGameplay()
{
start();
loadSpectatingScreen();
AddStep("advance frame count", () => nextFrame = 300);
sendFrames();
waitForPlayer();
AddUntilStep("playing from correct point in time", () => player.ChildrenOfType<DrawableRuleset>().First().FrameStableClock.CurrentTime > 30000);
}
[Test]
public void TestHostRetriesWhileWatching()
{
loadSpectatingScreen();
start();
sendFrames();
waitForPlayer();
Player lastPlayer = null;
AddStep("store first player", () => lastPlayer = player);
start();
sendFrames();
waitForPlayer();
AddAssert("player is different", () => lastPlayer != player);
}
[Test]
public void TestHostFails()
{
loadSpectatingScreen();
start();
waitForPlayer();
checkPaused(true);
finish();
checkPaused(false);
// TODO: should replay until running out of frames then fail
}
[Test]
public void TestStopWatchingDuringPlay()
{
loadSpectatingScreen();
start();
sendFrames();
waitForPlayer();
AddStep("stop spectating", () => (Stack.CurrentScreen as Player)?.Exit());
AddUntilStep("spectating stopped", () => spectatorScreen.GetChildScreen() == null);
}
[Test]
public void TestStopWatchingThenHostRetries()
{
loadSpectatingScreen();
start();
sendFrames();
waitForPlayer();
AddStep("stop spectating", () => (Stack.CurrentScreen as Player)?.Exit());
AddUntilStep("spectating stopped", () => spectatorScreen.GetChildScreen() == null);
// host starts playing a new session
start();
waitForPlayer();
}
[Test]
public void TestWatchingBeatmapThatDoesntExistLocally()
{
loadSpectatingScreen();
start(-1234);
sendFrames();
AddAssert("screen didn't change", () => Stack.CurrentScreen is Spectator);
}
private OsuFramedReplayInputHandler replayHandler =>
(OsuFramedReplayInputHandler)Stack.ChildrenOfType<OsuInputManager>().First().ReplayInputHandler;
private Player player => Stack.CurrentScreen as Player;
private double currentFrameStableTime
=> player.ChildrenOfType<FrameStabilityContainer>().First().FrameStableClock.CurrentTime;
private void waitForPlayer() => AddUntilStep("wait for player", () => Stack.CurrentScreen is Player);
private void start(int? beatmapId = null) => AddStep("start play", () => testSpectatorStreamingClient.StartPlay(beatmapId ?? importedBeatmapId));
private void finish(int? beatmapId = null) => AddStep("end play", () => testSpectatorStreamingClient.EndPlay(beatmapId ?? importedBeatmapId));
private void checkPaused(bool state) =>
AddUntilStep($"game is {(state ? "paused" : "playing")}", () => player.ChildrenOfType<DrawableRuleset>().First().IsPaused.Value == state);
private void sendFrames(int count = 10)
{
AddStep("send frames", () =>
{
testSpectatorStreamingClient.SendFrames(nextFrame, count);
nextFrame += count;
});
}
private void loadSpectatingScreen()
{
AddStep("load screen", () => LoadScreen(spectatorScreen = new Spectator(testSpectatorStreamingClient.StreamingUser)));
AddUntilStep("wait for screen load", () => spectatorScreen.LoadState == LoadState.Loaded);
}
public class TestSpectatorStreamingClient : SpectatorStreamingClient
{
public readonly User StreamingUser = new User { Id = 1234, Username = "Test user" };
public new BindableList<int> PlayingUsers => (BindableList<int>)base.PlayingUsers;
private int beatmapId;
protected override Task Connect()
{
return Task.CompletedTask;
}
public void StartPlay(int beatmapId)
{
this.beatmapId = beatmapId;
sendState(beatmapId);
}
public void EndPlay(int beatmapId)
{
((ISpectatorClient)this).UserFinishedPlaying(StreamingUser.Id, new SpectatorState
{
BeatmapID = beatmapId,
RulesetID = 0,
});
sentState = false;
}
private bool sentState;
public void SendFrames(int index, int count)
{
var frames = new List<LegacyReplayFrame>();
for (int i = index; i < index + count; i++)
{
var buttonState = i == index + count - 1 ? ReplayButtonState.None : ReplayButtonState.Left1;
frames.Add(new LegacyReplayFrame(i * 100, RNG.Next(0, 512), RNG.Next(0, 512), buttonState));
}
var bundle = new FrameDataBundle(frames);
((ISpectatorClient)this).UserSentFrames(StreamingUser.Id, bundle);
if (!sentState)
sendState(beatmapId);
}
public override void WatchUser(int userId)
{
if (sentState)
{
// usually the server would do this.
sendState(beatmapId);
}
base.WatchUser(userId);
}
private void sendState(int beatmapId)
{
sentState = true;
((ISpectatorClient)this).UserBeganPlaying(StreamingUser.Id, new SpectatorState
{
BeatmapID = beatmapId,
RulesetID = 0,
});
}
}
}
}

View File

@ -0,0 +1,361 @@
// 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.Collections.Specialized;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Framework.Input.StateChanges;
using osu.Framework.Logging;
using osu.Framework.Testing;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Graphics.Sprites;
using osu.Game.Online.API;
using osu.Game.Online.Spectator;
using osu.Game.Replays;
using osu.Game.Replays.Legacy;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.Replays.Types;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.Play;
using osu.Game.Tests.Visual.UserInterface;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Tests.Visual.Gameplay
{
public class TestSceneSpectatorPlayback : OsuManualInputManagerTestScene
{
protected override bool UseOnlineAPI => true;
private TestRulesetInputManager playbackManager;
private TestRulesetInputManager recordingManager;
private Replay replay;
private readonly IBindableList<int> users = new BindableList<int>();
private TestReplayRecorder recorder;
private readonly ManualClock manualClock = new ManualClock();
private OsuSpriteText latencyDisplay;
private TestFramedReplayInputHandler replayHandler;
[Resolved]
private IAPIProvider api { get; set; }
[Resolved]
private SpectatorStreamingClient streamingClient { get; set; }
[Cached]
private GameplayBeatmap gameplayBeatmap = new GameplayBeatmap(new Beatmap());
[SetUp]
public void SetUp() => Schedule(() =>
{
replay = new Replay();
users.BindTo(streamingClient.PlayingUsers);
users.BindCollectionChanged((obj, args) =>
{
switch (args.Action)
{
case NotifyCollectionChangedAction.Add:
foreach (int user in args.NewItems)
{
if (user == api.LocalUser.Value.Id)
streamingClient.WatchUser(user);
}
break;
case NotifyCollectionChangedAction.Remove:
foreach (int user in args.OldItems)
{
if (user == api.LocalUser.Value.Id)
streamingClient.StopWatchingUser(user);
}
break;
}
}, true);
streamingClient.OnNewFrames += onNewFrames;
Add(new GridContainer
{
RelativeSizeAxes = Axes.Both,
Content = new[]
{
new Drawable[]
{
recordingManager = new TestRulesetInputManager(new TestSceneModSettings.TestRulesetInfo(), 0, SimultaneousBindingMode.Unique)
{
Recorder = recorder = new TestReplayRecorder
{
ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos),
},
Child = new Container
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
new Box
{
Colour = Color4.Brown,
RelativeSizeAxes = Axes.Both,
},
new OsuSpriteText
{
Text = "Sending",
Scale = new Vector2(3),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
new TestInputConsumer()
}
},
}
},
new Drawable[]
{
playbackManager = new TestRulesetInputManager(new TestSceneModSettings.TestRulesetInfo(), 0, SimultaneousBindingMode.Unique)
{
Clock = new FramedClock(manualClock),
ReplayInputHandler = replayHandler = new TestFramedReplayInputHandler(replay)
{
GamefieldToScreenSpace = pos => playbackManager.ToScreenSpace(pos),
},
Child = new Container
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
new Box
{
Colour = Color4.DarkBlue,
RelativeSizeAxes = Axes.Both,
},
new OsuSpriteText
{
Text = "Receiving",
Scale = new Vector2(3),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
new TestInputConsumer()
}
},
}
}
}
});
Add(latencyDisplay = new OsuSpriteText());
});
private void onNewFrames(int userId, FrameDataBundle frames)
{
Logger.Log($"Received {frames.Frames.Count()} new frames ({string.Join(',', frames.Frames.Select(f => ((int)f.Time).ToString()))})");
foreach (var legacyFrame in frames.Frames)
{
var frame = new TestReplayFrame();
frame.FromLegacy(legacyFrame, null, null);
replay.Frames.Add(frame);
}
}
[Test]
public void TestBasic()
{
}
private double latency = SpectatorStreamingClient.TIME_BETWEEN_SENDS;
protected override void Update()
{
base.Update();
if (latencyDisplay == null) return;
// propagate initial time value
if (manualClock.CurrentTime == 0)
{
manualClock.CurrentTime = Time.Current;
return;
}
if (replayHandler.NextFrame != null)
{
var lastFrame = replay.Frames.LastOrDefault();
// this isn't perfect as we basically can't be aware of the rate-of-send here (the streamer is not sending data when not being moved).
// in gameplay playback, the case where NextFrame is null would pause gameplay and handle this correctly; it's strictly a test limitation / best effort implementation.
if (lastFrame != null)
latency = Math.Max(latency, Time.Current - lastFrame.Time);
latencyDisplay.Text = $"latency: {latency:N1}";
double proposedTime = Time.Current - latency + Time.Elapsed;
// this will either advance by one or zero frames.
double? time = replayHandler.SetFrameFromTime(proposedTime);
if (time == null)
return;
manualClock.CurrentTime = time.Value;
}
}
[TearDownSteps]
public void TearDown()
{
AddStep("stop recorder", () =>
{
recorder.Expire();
streamingClient.OnNewFrames -= onNewFrames;
});
}
public class TestFramedReplayInputHandler : FramedReplayInputHandler<TestReplayFrame>
{
public TestFramedReplayInputHandler(Replay replay)
: base(replay)
{
}
public override void CollectPendingInputs(List<IInput> inputs)
{
inputs.Add(new MousePositionAbsoluteInput { Position = GamefieldToScreenSpace(CurrentFrame?.Position ?? Vector2.Zero) });
inputs.Add(new ReplayState<TestAction> { PressedActions = CurrentFrame?.Actions ?? new List<TestAction>() });
}
}
public class TestInputConsumer : CompositeDrawable, IKeyBindingHandler<TestAction>
{
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Parent.ReceivePositionalInputAt(screenSpacePos);
private readonly Box box;
public TestInputConsumer()
{
Size = new Vector2(30);
Origin = Anchor.Centre;
InternalChildren = new Drawable[]
{
box = new Box
{
Colour = Color4.Black,
RelativeSizeAxes = Axes.Both,
},
};
}
protected override bool OnMouseMove(MouseMoveEvent e)
{
Position = e.MousePosition;
return base.OnMouseMove(e);
}
public bool OnPressed(TestAction action)
{
box.Colour = Color4.White;
return true;
}
public void OnReleased(TestAction action)
{
box.Colour = Color4.Black;
}
}
public class TestRulesetInputManager : RulesetInputManager<TestAction>
{
public TestRulesetInputManager(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique)
: base(ruleset, variant, unique)
{
}
protected override KeyBindingContainer<TestAction> CreateKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique)
=> new TestKeyBindingContainer();
internal class TestKeyBindingContainer : KeyBindingContainer<TestAction>
{
public override IEnumerable<KeyBinding> DefaultKeyBindings => new[]
{
new KeyBinding(InputKey.MouseLeft, TestAction.Down),
};
}
}
public class TestReplayFrame : ReplayFrame, IConvertibleReplayFrame
{
public Vector2 Position;
public List<TestAction> Actions = new List<TestAction>();
public TestReplayFrame(double time, Vector2 position, params TestAction[] actions)
: base(time)
{
Position = position;
Actions.AddRange(actions);
}
public TestReplayFrame()
{
}
public void FromLegacy(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayFrame lastFrame = null)
{
Position = currentFrame.Position;
Time = currentFrame.Time;
if (currentFrame.MouseLeft)
Actions.Add(TestAction.Down);
}
public LegacyReplayFrame ToLegacy(IBeatmap beatmap)
{
ReplayButtonState state = ReplayButtonState.None;
if (Actions.Contains(TestAction.Down))
state |= ReplayButtonState.Left1;
return new LegacyReplayFrame(Time, Position.X, Position.Y, state);
}
}
public enum TestAction
{
Down,
}
internal class TestReplayRecorder : ReplayRecorder<TestAction>
{
public TestReplayRecorder()
: base(new Replay())
{
}
protected override ReplayFrame HandleFrame(Vector2 mousePosition, List<TestAction> actions, ReplayFrame previousFrame)
{
return new TestReplayFrame(Time.Current, mousePosition, actions.ToArray());
}
}
}
}

View File

@ -0,0 +1,53 @@
// 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 NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Utils;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osuTK;
namespace osu.Game.Tests.Visual.Gameplay
{
[TestFixture]
public class TestSceneStarCounter : OsuTestScene
{
private readonly StarCounter starCounter;
private readonly OsuSpriteText starsLabel;
public TestSceneStarCounter()
{
starCounter = new StarCounter
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
};
Add(starCounter);
starsLabel = new OsuSpriteText
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Scale = new Vector2(2),
Y = 50,
};
Add(starsLabel);
setStars(5);
AddRepeatStep("random value", () => setStars(RNG.NextSingle() * (starCounter.StarCount + 1)), 10);
AddSliderStep("exact value", 0f, 10f, 5f, setStars);
AddStep("stop animation", () => starCounter.StopAnimation());
AddStep("reset", () => setStars(0));
}
private void setStars(float stars)
{
starCounter.Current = stars;
starsLabel.Text = starCounter.Current.ToString("0.00");
}
}
}

View File

@ -9,8 +9,12 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Formats;
using osu.Game.IO;
using osu.Game.Overlays;
using osu.Game.Storyboards;
using osu.Game.Storyboards.Drawables;
using osu.Game.Tests.Resources;
using osuTK.Graphics;
namespace osu.Game.Tests.Visual.Gameplay
@ -18,19 +22,32 @@ namespace osu.Game.Tests.Visual.Gameplay
[TestFixture]
public class TestSceneStoryboard : OsuTestScene
{
private readonly Container<DrawableStoryboard> storyboardContainer;
private Container<DrawableStoryboard> storyboardContainer;
private DrawableStoryboard storyboard;
[Cached]
private MusicController musicController = new MusicController();
[Test]
public void TestStoryboard()
{
AddStep("Restart", restart);
AddToggleStep("Passing", passing =>
{
if (storyboard != null) storyboard.Passing = passing;
});
}
public TestSceneStoryboard()
[Test]
public void TestStoryboardMissingVideo()
{
AddStep("Load storyboard with missing video", loadStoryboardNoVideo);
}
[BackgroundDependencyLoader]
private void load()
{
Clock = new FramedClock();
AddRange(new Drawable[]
{
musicController,
new Container
{
RelativeSizeAxes = Axes.Both,
@ -55,21 +72,10 @@ namespace osu.Game.Tests.Visual.Gameplay
}
});
AddStep("Restart", restart);
AddToggleStep("Passing", passing =>
{
if (storyboard != null) storyboard.Passing = passing;
});
Beatmap.BindValueChanged(beatmapChanged, true);
}
[BackgroundDependencyLoader]
private void load()
{
Beatmap.ValueChanged += beatmapChanged;
}
private void beatmapChanged(ValueChangedEvent<WorkingBeatmap> e)
=> loadStoryboard(e.NewValue);
private void beatmapChanged(ValueChangedEvent<WorkingBeatmap> e) => loadStoryboard(e.NewValue);
private void restart()
{
@ -94,5 +100,28 @@ namespace osu.Game.Tests.Visual.Gameplay
storyboardContainer.Add(storyboard);
decoupledClock.ChangeSource(working.Track);
}
private void loadStoryboardNoVideo()
{
if (storyboard != null)
storyboardContainer.Remove(storyboard);
var decoupledClock = new DecoupleableInterpolatingFramedClock { IsCoupled = true };
storyboardContainer.Clock = decoupledClock;
Storyboard sb;
using (var str = TestResources.OpenResource("storyboard_no_video.osu"))
using (var bfr = new LineBufferedReader(str))
{
var decoder = new LegacyStoryboardDecoder();
sb = decoder.Decode(bfr);
}
storyboard = sb.CreateDrawable(Beatmap.Value);
storyboardContainer.Add(storyboard);
decoupledClock.ChangeSource(Beatmap.Value.Track);
}
}
}

View File

@ -1,8 +1,6 @@
// 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 NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
@ -18,21 +16,13 @@ namespace osu.Game.Tests.Visual.Menus
[TestFixture]
public abstract class IntroTestScene : OsuTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(StartupScreen),
typeof(IntroScreen),
typeof(OsuScreen),
typeof(IntroTestScene),
};
[Cached]
private OsuLogo logo;
protected OsuScreenStack IntroStack;
protected IntroTestScene()
{
Drawable introStack = null;
Children = new Drawable[]
{
new Box
@ -55,13 +45,17 @@ namespace osu.Game.Tests.Visual.Menus
logo.FinishTransforms();
logo.IsTracking = false;
introStack?.Expire();
IntroStack?.Expire();
Add(introStack = new OsuScreenStack(CreateScreen())
Add(IntroStack = new OsuScreenStack
{
RelativeSizeAxes = Axes.Both,
});
IntroStack.Push(CreateScreen());
});
AddUntilStep("wait for menu", () => IntroStack.CurrentScreen is MainMenu);
}
protected abstract IScreen CreateScreen();

View File

@ -2,7 +2,6 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Game.Online.API;
using osu.Game.Screens.Menu;
using osu.Game.Users;
@ -11,17 +10,17 @@ namespace osu.Game.Tests.Visual.Menus
public class TestSceneDisclaimer : ScreenTestScene
{
[BackgroundDependencyLoader]
private void load(IAPIProvider api)
private void load()
{
AddStep("load disclaimer", () => LoadScreen(new Disclaimer()));
AddStep("toggle support", () =>
{
api.LocalUser.Value = new User
API.LocalUser.Value = new User
{
Username = api.LocalUser.Value.Username,
Id = api.LocalUser.Value.Id,
IsSupporter = !api.LocalUser.Value.IsSupporter,
Username = API.LocalUser.Value.Username,
Id = API.LocalUser.Value.Id + 1,
IsSupporter = !API.LocalUser.Value.IsSupporter,
};
});
}

View File

@ -0,0 +1,23 @@
// 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 NUnit.Framework;
using osu.Framework.Screens;
using osu.Framework.Utils;
using osu.Game.Screens.Menu;
namespace osu.Game.Tests.Visual.Menus
{
[TestFixture]
public class TestSceneIntroWelcome : IntroTestScene
{
protected override IScreen CreateScreen() => new IntroWelcome();
public TestSceneIntroWelcome()
{
AddUntilStep("wait for load", () => MusicController.TrackLoaded);
AddAssert("correct track", () => Precision.AlmostEquals(MusicController.CurrentTrack.Length, 48000, 1));
AddAssert("check if menu music loops", () => MusicController.CurrentTrack.Looping);
}
}
}

View File

@ -1,12 +1,15 @@
// 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 System.Threading;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Game.Graphics.UserInterface;
using osu.Game.Screens;
using osu.Game.Screens.Menu;
using osuTK.Graphics;
@ -14,14 +17,14 @@ using osuTK.Graphics;
namespace osu.Game.Tests.Visual.Menus
{
[TestFixture]
public class TestSceneLoaderAnimation : ScreenTestScene
public class TestSceneLoader : ScreenTestScene
{
private TestLoader loader;
[Cached]
private OsuLogo logo;
public TestSceneLoaderAnimation()
public TestSceneLoader()
{
Child = logo = new OsuLogo
{
@ -33,9 +36,7 @@ namespace osu.Game.Tests.Visual.Menus
[Test]
public void TestInstantLoad()
{
bool logoVisible = false;
AddStep("begin loading", () =>
AddStep("load immediately", () =>
{
loader = new TestLoader();
loader.AllowLoad.Set();
@ -43,40 +44,37 @@ namespace osu.Game.Tests.Visual.Menus
LoadScreen(loader);
});
AddUntilStep("loaded", () =>
{
logoVisible = loader.Logo?.Alpha > 0;
return loader.Logo != null && loader.ScreenLoaded;
});
spinnerNotPresentOrHidden();
AddAssert("logo was not visible", () => !logoVisible);
AddUntilStep("loaded", () => loader.ScreenLoaded);
AddUntilStep("not current", () => !loader.IsCurrentScreen());
spinnerNotPresentOrHidden();
}
private void spinnerNotPresentOrHidden() =>
AddAssert("spinner did not display", () => loader.LoadingSpinner == null || loader.LoadingSpinner.Alpha == 0);
[Test]
public void TestDelayedLoad()
{
AddStep("begin loading", () => LoadScreen(loader = new TestLoader()));
AddUntilStep("wait for logo visible", () => loader.Logo?.Alpha > 0);
AddUntilStep("wait for spinner visible", () => loader.LoadingSpinner?.Alpha > 0);
AddStep("finish loading", () => loader.AllowLoad.Set());
AddAssert("loaded", () => loader.Logo != null && loader.ScreenLoaded);
AddUntilStep("logo gone", () => loader.Logo?.Alpha == 0);
AddUntilStep("spinner gone", () => loader.LoadingSpinner?.Alpha == 0);
AddUntilStep("loaded", () => loader.ScreenLoaded);
AddUntilStep("not current", () => !loader.IsCurrentScreen());
}
private class TestLoader : Loader
{
public readonly ManualResetEventSlim AllowLoad = new ManualResetEventSlim();
public OsuLogo Logo;
public LoadingSpinner LoadingSpinner => this.ChildrenOfType<LoadingSpinner>().FirstOrDefault();
private TestScreen screen;
public bool ScreenLoaded => screen.IsCurrentScreen();
protected override void LogoArriving(OsuLogo logo, bool resuming)
{
Logo = logo;
base.LogoArriving(logo, resuming);
}
protected override OsuScreen CreateLoadableScreen() => screen = new TestScreen();
protected override ShaderPrecompiler CreateShaderPrecompiler() => new TestShaderPrecompiler(AllowLoad);

View File

@ -0,0 +1,84 @@
// 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.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Database;
using osu.Game.Input.Bindings;
using osu.Game.Overlays;
using osu.Game.Tests.Resources;
using osu.Game.Tests.Visual.Navigation;
namespace osu.Game.Tests.Visual.Menus
{
public class TestSceneMusicActionHandling : OsuGameTestScene
{
private GlobalActionContainer globalActionContainer => Game.ChildrenOfType<GlobalActionContainer>().First();
[Test]
public void TestMusicPlayAction()
{
AddStep("ensure playing something", () => Game.MusicController.EnsurePlayingSomething());
AddStep("toggle playback", () => globalActionContainer.TriggerPressed(GlobalAction.MusicPlay));
AddAssert("music paused", () => !Game.MusicController.IsPlaying && Game.MusicController.UserPauseRequested);
AddStep("toggle playback", () => globalActionContainer.TriggerPressed(GlobalAction.MusicPlay));
AddAssert("music resumed", () => Game.MusicController.IsPlaying && !Game.MusicController.UserPauseRequested);
}
[Test]
public void TestMusicNavigationActions()
{
int importId = 0;
Queue<(WorkingBeatmap working, TrackChangeDirection changeDirection)> trackChangeQueue = null;
// ensure we have at least two beatmaps available to identify the direction the music controller navigated to.
AddRepeatStep("import beatmap", () => Game.BeatmapManager.Import(new BeatmapSetInfo
{
Beatmaps = new List<BeatmapInfo>
{
new BeatmapInfo
{
BaseDifficulty = new BeatmapDifficulty(),
}
},
Metadata = new BeatmapMetadata
{
Artist = $"a test map {importId++}",
Title = "title",
}
}).Wait(), 5);
AddStep("import beatmap with track", () =>
{
var setWithTrack = Game.BeatmapManager.Import(new ImportTask(TestResources.GetTestBeatmapForImport())).Result;
Beatmap.Value = Game.BeatmapManager.GetWorkingBeatmap(setWithTrack.Beatmaps.First());
});
AddStep("bind to track change", () =>
{
trackChangeQueue = new Queue<(WorkingBeatmap, TrackChangeDirection)>();
Game.MusicController.TrackChanged += (working, changeDirection) => trackChangeQueue.Enqueue((working, changeDirection));
});
AddStep("seek track to 6 second", () => Game.MusicController.SeekTo(6000));
AddUntilStep("wait for current time to update", () => Game.MusicController.CurrentTrack.CurrentTime > 5000);
AddStep("press previous", () => globalActionContainer.TriggerPressed(GlobalAction.MusicPrev));
AddAssert("no track change", () => trackChangeQueue.Count == 0);
AddUntilStep("track restarted", () => Game.MusicController.CurrentTrack.CurrentTime < 5000);
AddStep("press previous", () => globalActionContainer.TriggerPressed(GlobalAction.MusicPrev));
AddAssert("track changed to previous", () =>
trackChangeQueue.Count == 1 &&
trackChangeQueue.Dequeue().changeDirection == TrackChangeDirection.Prev);
AddStep("press next", () => globalActionContainer.TriggerPressed(GlobalAction.MusicNext));
AddAssert("track changed to next", () =>
trackChangeQueue.Count == 1 &&
trackChangeQueue.Dequeue().changeDirection == TrackChangeDirection.Next);
}
}
}

View File

@ -0,0 +1,31 @@
// 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 osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Overlays;
using osu.Game.Screens.Menu;
namespace osu.Game.Tests.Visual.Menus
{
public class TestSceneSongTicker : OsuTestScene
{
public TestSceneSongTicker()
{
AddRange(new Drawable[]
{
new SongTicker
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
new NowPlayingOverlay
{
Origin = Anchor.TopRight,
Anchor = Anchor.TopRight,
State = { Value = Visibility.Visible }
}
});
}
}
}

View File

@ -1,44 +1,95 @@
// 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.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Game.Overlays;
using osu.Game.Overlays.Toolbar;
using osu.Game.Rulesets;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Menus
{
[TestFixture]
public class TestSceneToolbar : OsuTestScene
public class TestSceneToolbar : OsuManualInputManagerTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(ToolbarButton),
typeof(ToolbarRulesetSelector),
typeof(ToolbarRulesetTabButton),
typeof(ToolbarNotificationButton),
};
private TestToolbar toolbar;
public TestSceneToolbar()
[Resolved]
private RulesetStore rulesets { get; set; }
[SetUp]
public void SetUp() => Schedule(() =>
{
Child = toolbar = new TestToolbar { State = { Value = Visibility.Visible } };
});
[Test]
public void TestNotificationCounter()
{
var toolbar = new Toolbar { State = { Value = Visibility.Visible } };
ToolbarNotificationButton notificationButton = null;
AddStep("create toolbar", () =>
{
Add(toolbar);
notificationButton = toolbar.Children.OfType<FillFlowContainer>().Last().Children.OfType<ToolbarNotificationButton>().First();
});
void setNotifications(int count) => AddStep($"set notification count to {count}", () => notificationButton.NotificationCount.Value = count);
AddStep("retrieve notification button", () => notificationButton = toolbar.ChildrenOfType<ToolbarNotificationButton>().Single());
setNotifications(1);
setNotifications(2);
setNotifications(3);
setNotifications(0);
setNotifications(144);
void setNotifications(int count)
=> AddStep($"set notification count to {count}",
() => notificationButton.NotificationCount.Value = count);
}
[TestCase(false)]
[TestCase(true)]
public void TestRulesetSwitchingShortcut(bool toolbarHidden)
{
ToolbarRulesetSelector rulesetSelector = null;
if (toolbarHidden)
AddStep("hide toolbar", () => toolbar.Hide());
AddStep("retrieve ruleset selector", () => rulesetSelector = toolbar.ChildrenOfType<ToolbarRulesetSelector>().Single());
for (int i = 0; i < 4; i++)
{
var expected = rulesets.AvailableRulesets.ElementAt(i);
var numberKey = Key.Number1 + i;
AddStep($"switch to ruleset {i} via shortcut", () =>
{
InputManager.PressKey(Key.ControlLeft);
InputManager.Key(numberKey);
InputManager.ReleaseKey(Key.ControlLeft);
});
AddUntilStep("ruleset switched", () => rulesetSelector.Current.Value.Equals(expected));
}
}
[TestCase(OverlayActivation.All)]
[TestCase(OverlayActivation.Disabled)]
public void TestRespectsOverlayActivation(OverlayActivation mode)
{
AddStep($"set activation mode to {mode}", () => toolbar.OverlayActivationMode.Value = mode);
AddStep("hide toolbar", () => toolbar.Hide());
AddStep("try to show toolbar", () => toolbar.Show());
if (mode == OverlayActivation.Disabled)
AddAssert("toolbar still hidden", () => toolbar.State.Value == Visibility.Hidden);
else
AddAssert("toolbar is visible", () => toolbar.State.Value == Visibility.Visible);
}
public class TestToolbar : Toolbar
{
public new Bindable<OverlayActivation> OverlayActivationMode => base.OverlayActivationMode as Bindable<OverlayActivation>;
}
}
}

View File

@ -0,0 +1,61 @@
// 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 osu.Framework.Allocation;
using osu.Game.Beatmaps;
using osu.Game.Online.Multiplayer;
using osu.Game.Rulesets;
using osu.Game.Screens.Multi;
using osu.Game.Users;
namespace osu.Game.Tests.Visual.Multiplayer
{
public abstract class RoomManagerTestScene : MultiplayerTestScene
{
[Cached(Type = typeof(IRoomManager))]
protected TestRoomManager RoomManager { get; } = new TestRoomManager();
public override void SetUpSteps()
{
base.SetUpSteps();
AddStep("clear rooms", () => RoomManager.Rooms.Clear());
}
protected void AddRooms(int count, RulesetInfo ruleset = null)
{
AddStep("add rooms", () =>
{
for (int i = 0; i < count; i++)
{
var room = new Room
{
RoomID = { Value = i },
Name = { Value = $"Room {i}" },
Host = { Value = new User { Username = "Host" } },
EndDate = { Value = DateTimeOffset.Now + TimeSpan.FromSeconds(10) },
Category = { Value = i % 2 == 0 ? RoomCategory.Spotlight : RoomCategory.Normal }
};
if (ruleset != null)
{
room.Playlist.Add(new PlaylistItem
{
Ruleset = { Value = ruleset },
Beatmap =
{
Value = new BeatmapInfo
{
Metadata = new BeatmapMetadata()
}
}
});
}
RoomManager.Rooms.Add(room);
}
});
}
}
}

View File

@ -0,0 +1,35 @@
// 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 osu.Framework.Bindables;
using osu.Game.Online.Multiplayer;
using osu.Game.Screens.Multi;
namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestRoomManager : IRoomManager
{
public event Action RoomsUpdated
{
add { }
remove { }
}
public readonly BindableList<Room> Rooms = new BindableList<Room>();
public Bindable<bool> InitialRoomsReceived { get; } = new Bindable<bool>(true);
IBindableList<Room> IRoomManager.Rooms => Rooms;
public void CreateRoom(Room room, Action<Room> onSuccess = null, Action<string> onError = null) => Rooms.Add(room);
public void JoinRoom(Room room, Action<Room> onSuccess = null, Action<string> onError = null)
{
}
public void PartRoom()
{
}
}
}

View File

@ -0,0 +1,321 @@
// 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.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Platform;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.Multiplayer;
using osu.Game.Overlays;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Screens.Multi;
using osu.Game.Tests.Beatmaps;
using osuTK;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneDrawableRoomPlaylist : OsuManualInputManagerTestScene
{
private TestPlaylist playlist;
private BeatmapManager manager;
private RulesetStore rulesets;
[BackgroundDependencyLoader]
private void load(GameHost host, AudioManager audio)
{
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, host, Beatmap.Default));
manager.Import(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo.BeatmapSet).Wait();
}
[Test]
public void TestNonEditableNonSelectable()
{
createPlaylist(false, false);
moveToItem(0);
assertHandleVisibility(0, false);
assertDeleteButtonVisibility(0, false);
AddStep("click", () => InputManager.Click(MouseButton.Left));
AddAssert("no item selected", () => playlist.SelectedItem.Value == null);
}
[Test]
public void TestEditable()
{
createPlaylist(true, false);
moveToItem(0);
assertHandleVisibility(0, true);
assertDeleteButtonVisibility(0, true);
AddStep("click", () => InputManager.Click(MouseButton.Left));
AddAssert("no item selected", () => playlist.SelectedItem.Value == null);
}
[Test]
public void TestSelectable()
{
createPlaylist(false, true);
moveToItem(0);
assertHandleVisibility(0, false);
assertDeleteButtonVisibility(0, false);
AddStep("click", () => InputManager.Click(MouseButton.Left));
AddAssert("item 0 is selected", () => playlist.SelectedItem.Value == playlist.Items[0]);
}
[Test]
public void TestEditableSelectable()
{
createPlaylist(true, true);
moveToItem(0);
assertHandleVisibility(0, true);
assertDeleteButtonVisibility(0, true);
AddStep("click", () => InputManager.Click(MouseButton.Left));
AddAssert("item 0 is selected", () => playlist.SelectedItem.Value == playlist.Items[0]);
}
[Test]
public void TestSelectionNotLostAfterRearrangement()
{
createPlaylist(true, true);
moveToItem(0);
AddStep("click", () => InputManager.Click(MouseButton.Left));
moveToDragger(0);
AddStep("begin drag", () => InputManager.PressButton(MouseButton.Left));
moveToDragger(1, new Vector2(0, 5));
AddStep("end drag", () => InputManager.ReleaseButton(MouseButton.Left));
AddAssert("item 1 is selected", () => playlist.SelectedItem.Value == playlist.Items[1]);
}
[Test]
public void TestItemRemovedOnDeletion()
{
PlaylistItem selectedItem = null;
createPlaylist(true, true);
moveToItem(0);
AddStep("click", () => InputManager.Click(MouseButton.Left));
AddStep("retrieve selection", () => selectedItem = playlist.SelectedItem.Value);
moveToDeleteButton(0);
AddStep("click delete button", () => InputManager.Click(MouseButton.Left));
AddAssert("item removed", () => !playlist.Items.Contains(selectedItem));
}
[Test]
public void TestNextItemSelectedAfterDeletion()
{
createPlaylist(true, true);
moveToItem(0);
AddStep("click", () => InputManager.Click(MouseButton.Left));
moveToDeleteButton(0);
AddStep("click delete button", () => InputManager.Click(MouseButton.Left));
AddAssert("item 0 is selected", () => playlist.SelectedItem.Value == playlist.Items[0]);
}
[Test]
public void TestLastItemSelectedAfterLastItemDeleted()
{
createPlaylist(true, true);
AddWaitStep("wait for flow", 5); // Items may take 1 update frame to flow. A wait count of 5 is guaranteed to result in the flow being updated as desired.
AddStep("scroll to bottom", () => playlist.ChildrenOfType<ScrollContainer<Drawable>>().First().ScrollToEnd(false));
moveToItem(19);
AddStep("click", () => InputManager.Click(MouseButton.Left));
moveToDeleteButton(19);
AddStep("click delete button", () => InputManager.Click(MouseButton.Left));
AddAssert("item 18 is selected", () => playlist.SelectedItem.Value == playlist.Items[18]);
}
[Test]
public void TestSelectionResetWhenAllItemsDeleted()
{
createPlaylist(true, true);
AddStep("remove all but one item", () =>
{
playlist.Items.RemoveRange(1, playlist.Items.Count - 1);
});
moveToItem(0);
AddStep("click", () => InputManager.Click(MouseButton.Left));
moveToDeleteButton(0);
AddStep("click delete button", () => InputManager.Click(MouseButton.Left));
AddAssert("no item selected", () => playlist.SelectedItem.Value == null);
}
// Todo: currently not possible due to bindable list shortcomings (https://github.com/ppy/osu-framework/issues/3081)
// [Test]
public void TestNextItemSelectedAfterExternalDeletion()
{
createPlaylist(true, true);
moveToItem(0);
AddStep("click", () => InputManager.Click(MouseButton.Left));
AddStep("remove item 0", () => playlist.Items.RemoveAt(0));
AddAssert("item 0 is selected", () => playlist.SelectedItem.Value == playlist.Items[0]);
}
[Test]
public void TestChangeBeatmapAndRemove()
{
createPlaylist(true, true);
AddStep("change beatmap of first item", () => playlist.Items[0].BeatmapID = 30);
moveToDeleteButton(0);
AddStep("click delete button", () => InputManager.Click(MouseButton.Left));
}
[Test]
public void TestDownloadButtonHiddenInitiallyWhenBeatmapExists()
{
createPlaylist(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo);
AddAssert("download button hidden", () => !playlist.ChildrenOfType<BeatmapDownloadTrackingComposite>().Single().IsPresent);
}
[Test]
public void TestDownloadButtonVisibleInitiallyWhenBeatmapDoesNotExist()
{
var byOnlineId = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo;
byOnlineId.BeatmapSet.OnlineBeatmapSetID = 1337; // Some random ID that does not exist locally.
var byChecksum = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo;
byChecksum.MD5Hash = "1337"; // Some random checksum that does not exist locally.
createPlaylist(byOnlineId, byChecksum);
AddAssert("download buttons shown", () => playlist.ChildrenOfType<BeatmapDownloadTrackingComposite>().All(d => d.IsPresent));
}
private void moveToItem(int index, Vector2? offset = null)
=> AddStep($"move mouse to item {index}", () => InputManager.MoveMouseTo(playlist.ChildrenOfType<OsuRearrangeableListItem<PlaylistItem>>().ElementAt(index), offset));
private void moveToDragger(int index, Vector2? offset = null) => AddStep($"move mouse to dragger {index}", () =>
{
var item = playlist.ChildrenOfType<OsuRearrangeableListItem<PlaylistItem>>().ElementAt(index);
InputManager.MoveMouseTo(item.ChildrenOfType<OsuRearrangeableListItem<PlaylistItem>.PlaylistItemHandle>().Single(), offset);
});
private void moveToDeleteButton(int index, Vector2? offset = null) => AddStep($"move mouse to delete button {index}", () =>
{
var item = playlist.ChildrenOfType<OsuRearrangeableListItem<PlaylistItem>>().ElementAt(index);
InputManager.MoveMouseTo(item.ChildrenOfType<IconButton>().ElementAt(0), offset);
});
private void assertHandleVisibility(int index, bool visible)
=> AddAssert($"handle {index} {(visible ? "is" : "is not")} visible",
() => (playlist.ChildrenOfType<OsuRearrangeableListItem<PlaylistItem>.PlaylistItemHandle>().ElementAt(index).Alpha > 0) == visible);
private void assertDeleteButtonVisibility(int index, bool visible)
=> AddAssert($"delete button {index} {(visible ? "is" : "is not")} visible", () => (playlist.ChildrenOfType<IconButton>().ElementAt(2 + index * 2).Alpha > 0) == visible);
private void createPlaylist(bool allowEdit, bool allowSelection)
{
AddStep("create playlist", () =>
{
Child = playlist = new TestPlaylist(allowEdit, allowSelection)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(500, 300)
};
for (int i = 0; i < 20; i++)
{
playlist.Items.Add(new PlaylistItem
{
ID = i,
Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo },
Ruleset = { Value = new OsuRuleset().RulesetInfo },
RequiredMods =
{
new OsuModHardRock(),
new OsuModDoubleTime(),
new OsuModAutoplay()
}
});
}
});
AddUntilStep("wait for items to load", () => playlist.ItemMap.Values.All(i => i.IsLoaded));
}
private void createPlaylist(params BeatmapInfo[] beatmaps)
{
AddStep("create playlist", () =>
{
Child = playlist = new TestPlaylist(false, false)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(500, 300)
};
int index = 0;
foreach (var b in beatmaps)
{
playlist.Items.Add(new PlaylistItem
{
ID = index++,
Beatmap = { Value = b },
Ruleset = { Value = new OsuRuleset().RulesetInfo },
RequiredMods =
{
new OsuModHardRock(),
new OsuModDoubleTime(),
new OsuModAutoplay()
}
});
}
});
AddUntilStep("wait for items to load", () => playlist.ItemMap.Values.All(i => i.IsLoaded));
}
private class TestPlaylist : DrawableRoomPlaylist
{
public new IReadOnlyDictionary<PlaylistItem, RearrangeableListItem<PlaylistItem>> ItemMap => base.ItemMap;
public TestPlaylist(bool allowEdit, bool allowSelection)
: base(allowEdit, allowSelection)
{
}
}
}
}

View File

@ -0,0 +1,53 @@
// 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 NUnit.Framework;
using osu.Framework.Graphics;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Multiplayer.RoomStatuses;
using osu.Game.Screens.Multi.Lounge.Components;
using osu.Game.Users;
namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneLoungeRoomInfo : MultiplayerTestScene
{
[SetUp]
public void Setup() => Schedule(() =>
{
Room = new Room();
Child = new RoomInfo
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Width = 500
};
});
public override void SetUpSteps()
{
// Todo: Temp
}
[Test]
public void TestNonSelectedRoom()
{
AddStep("set null room", () => Room.RoomID.Value = null);
}
[Test]
public void TestOpenRoom()
{
AddStep("set open room", () =>
{
Room.RoomID.Value = 0;
Room.Name.Value = "Room 0";
Room.Host.Value = new User { Username = "peppy", Id = 2 };
Room.EndDate.Value = DateTimeOffset.Now.AddMonths(1);
Room.Status.Value = new RoomStatusOpen();
});
}
}
}

View File

@ -1,37 +1,27 @@
// 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.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Graphics;
using osu.Game.Online.Multiplayer;
using osu.Game.Screens.Multi;
using osu.Game.Rulesets.Catch;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Multi.Lounge.Components;
using osu.Game.Users;
using osuTK.Graphics;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneLoungeRoomsContainer : MultiplayerTestScene
public class TestSceneLoungeRoomsContainer : RoomManagerTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(RoomsContainer),
typeof(DrawableRoom)
};
[Cached(Type = typeof(IRoomManager))]
private TestRoomManager roomManager = new TestRoomManager();
private RoomsContainer container;
[BackgroundDependencyLoader]
private void load()
{
RoomsContainer container;
Child = container = new RoomsContainer
{
Anchor = Anchor.Centre,
@ -39,59 +29,88 @@ namespace osu.Game.Tests.Visual.Multiplayer
Width = 0.5f,
JoinRequested = joinRequested
};
}
AddStep("clear rooms", () => roomManager.Rooms.Clear());
[Test]
public void TestBasicListChanges()
{
AddRooms(3);
AddStep("add rooms", () =>
{
for (int i = 0; i < 3; i++)
{
roomManager.Rooms.Add(new Room
{
RoomID = { Value = i },
Name = { Value = $"Room {i}" },
Host = { Value = new User { Username = "Host" } },
EndDate = { Value = DateTimeOffset.Now + TimeSpan.FromSeconds(10) }
});
}
});
AddAssert("has 2 rooms", () => container.Rooms.Count == 3);
AddStep("remove first room", () => roomManager.Rooms.Remove(roomManager.Rooms.FirstOrDefault()));
AddAssert("has 3 rooms", () => container.Rooms.Count == 3);
AddStep("remove first room", () => RoomManager.Rooms.Remove(RoomManager.Rooms.FirstOrDefault()));
AddAssert("has 2 rooms", () => container.Rooms.Count == 2);
AddAssert("first room removed", () => container.Rooms.All(r => r.Room.RoomID.Value != 0));
AddStep("select first room", () => container.Rooms.First().Action?.Invoke());
AddAssert("first room selected", () => Room == roomManager.Rooms.First());
AddAssert("first room selected", () => checkRoomSelected(RoomManager.Rooms.First()));
AddStep("join first room", () => container.Rooms.First().Action?.Invoke());
AddAssert("first room joined", () => roomManager.Rooms.First().Status.Value is JoinedRoomStatus);
AddAssert("first room joined", () => RoomManager.Rooms.First().Status.Value is JoinedRoomStatus);
}
[Test]
public void TestKeyboardNavigation()
{
AddRooms(3);
AddAssert("no selection", () => checkRoomSelected(null));
press(Key.Down);
AddAssert("first room selected", () => checkRoomSelected(RoomManager.Rooms.First()));
press(Key.Up);
AddAssert("first room selected", () => checkRoomSelected(RoomManager.Rooms.First()));
press(Key.Down);
press(Key.Down);
AddAssert("last room selected", () => checkRoomSelected(RoomManager.Rooms.Last()));
press(Key.Enter);
AddAssert("last room joined", () => RoomManager.Rooms.Last().Status.Value is JoinedRoomStatus);
}
private void press(Key down)
{
AddStep($"press {down}", () => InputManager.Key(down));
}
[Test]
public void TestStringFiltering()
{
AddRooms(4);
AddUntilStep("4 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 4);
AddStep("filter one room", () => container.Filter(new FilterCriteria { SearchString = "1" }));
AddUntilStep("1 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 1);
AddStep("remove filter", () => container.Filter(null));
AddUntilStep("4 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 4);
}
[Test]
public void TestRulesetFiltering()
{
AddRooms(2, new OsuRuleset().RulesetInfo);
AddRooms(3, new CatchRuleset().RulesetInfo);
AddUntilStep("5 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 5);
AddStep("filter osu! rooms", () => container.Filter(new FilterCriteria { Ruleset = new OsuRuleset().RulesetInfo }));
AddUntilStep("2 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 2);
AddStep("filter catch rooms", () => container.Filter(new FilterCriteria { Ruleset = new CatchRuleset().RulesetInfo }));
AddUntilStep("3 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 3);
}
private bool checkRoomSelected(Room room) => Room == room;
private void joinRequested(Room room) => room.Status.Value = new JoinedRoomStatus();
private class TestRoomManager : IRoomManager
{
public event Action RoomsUpdated
{
add { }
remove { }
}
public readonly BindableList<Room> Rooms = new BindableList<Room>();
IBindableList<Room> IRoomManager.Rooms => Rooms;
public void CreateRoom(Room room, Action<Room> onSuccess = null, Action<string> onError = null) => Rooms.Add(room);
public void JoinRoom(Room room, Action<Room> onSuccess = null, Action<string> onError = null)
{
}
public void PartRoom()
{
}
}
private class JoinedRoomStatus : RoomStatus
{
public override string Message => "Joined";

View File

@ -0,0 +1,58 @@
// 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.Screens;
using osu.Framework.Testing;
using osu.Game.Graphics.Containers;
using osu.Game.Screens.Multi.Lounge;
using osu.Game.Screens.Multi.Lounge.Components;
namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneLoungeSubScreen : RoomManagerTestScene
{
private LoungeSubScreen loungeScreen;
[BackgroundDependencyLoader]
private void load()
{
}
public override void SetUpSteps()
{
base.SetUpSteps();
AddStep("push screen", () => LoadScreen(loungeScreen = new LoungeSubScreen
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Width = 0.5f,
}));
AddUntilStep("wait for present", () => loungeScreen.IsCurrentScreen());
}
private RoomsContainer roomsContainer => loungeScreen.ChildrenOfType<RoomsContainer>().First();
[Test]
public void TestScrollSelectedIntoView()
{
AddRooms(30);
AddUntilStep("first room is not masked", () => checkRoomVisible(roomsContainer.Rooms.First()));
AddStep("select last room", () => roomsContainer.Rooms.Last().Action?.Invoke());
AddUntilStep("first room is masked", () => !checkRoomVisible(roomsContainer.Rooms.First()));
AddUntilStep("last room is not masked", () => checkRoomVisible(roomsContainer.Rooms.Last()));
}
private bool checkRoomVisible(DrawableRoom room) =>
loungeScreen.ChildrenOfType<OsuScrollContainer>().First().ScreenSpaceDrawQuad
.Contains(room.ScreenSpaceDrawQuad.Centre);
}
}

View File

@ -0,0 +1,56 @@
// 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 NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Beatmaps;
using osu.Game.Online.Multiplayer;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Screens.Multi.Components;
using osu.Game.Tests.Beatmaps;
using osuTK;
namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneMatchBeatmapDetailArea : MultiplayerTestScene
{
[Resolved]
private BeatmapManager beatmapManager { get; set; }
[Resolved]
private RulesetStore rulesetStore { get; set; }
[SetUp]
public void Setup() => Schedule(() =>
{
Room = new Room();
Child = new MatchBeatmapDetailArea
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(500),
CreateNewItem = createNewItem
};
});
private void createNewItem()
{
Room.Playlist.Add(new PlaylistItem
{
ID = Room.Playlist.Count,
Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo },
Ruleset = { Value = new OsuRuleset().RulesetInfo },
RequiredMods =
{
new OsuModHardRock(),
new OsuModDoubleTime(),
new OsuModAutoplay()
}
});
}
}
}

View File

@ -1,53 +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.
using System;
using System.Collections.Generic;
using osu.Game.Beatmaps;
using osu.Game.Online.Multiplayer;
using osu.Game.Screens.Multi.Match.Components;
using osu.Framework.Graphics;
using osu.Framework.MathUtils;
using osu.Game.Audio;
using osu.Framework.Allocation;
namespace osu.Game.Tests.Visual.Multiplayer
{
[Cached(typeof(IPreviewTrackOwner))]
public class TestSceneMatchBeatmapPanel : MultiplayerTestScene, IPreviewTrackOwner
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(MatchBeatmapPanel)
};
[Resolved]
private PreviewTrackManager previewTrackManager { get; set; }
public TestSceneMatchBeatmapPanel()
{
Add(new MatchBeatmapPanel
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
});
Room.Playlist.Add(new PlaylistItem { Beatmap = new BeatmapInfo { OnlineBeatmapID = 1763072 } });
Room.Playlist.Add(new PlaylistItem { Beatmap = new BeatmapInfo { OnlineBeatmapID = 2101557 } });
Room.Playlist.Add(new PlaylistItem { Beatmap = new BeatmapInfo { OnlineBeatmapID = 1973466 } });
Room.Playlist.Add(new PlaylistItem { Beatmap = new BeatmapInfo { OnlineBeatmapID = 2109801 } });
Room.Playlist.Add(new PlaylistItem { Beatmap = new BeatmapInfo { OnlineBeatmapID = 1922035 } });
}
protected override void LoadComplete()
{
base.LoadComplete();
AddStep("Select random beatmap", () =>
{
Room.CurrentItem.Value = Room.Playlist[RNG.Next(Room.Playlist.Count)];
previewTrackManager.StopAnyPlaying(this);
});
}
}
}

View File

@ -1,38 +1,35 @@
// 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 osu.Game.Beatmaps;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Multiplayer.GameTypes;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Screens.Multi.Match.Components;
using osu.Game.Users;
namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneMatchHeader : MultiplayerTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(Header)
};
public TestSceneMatchHeader()
{
Room = new Room();
Room.Playlist.Add(new PlaylistItem
{
Beatmap = new BeatmapInfo
Beatmap =
{
Metadata = new BeatmapMetadata
Value = new BeatmapInfo
{
Title = "Title",
Artist = "Artist",
AuthorString = "Author",
},
Version = "Version",
Ruleset = new OsuRuleset().RulesetInfo
Metadata = new BeatmapMetadata
{
Title = "Title",
Artist = "Artist",
AuthorString = "Author",
},
Version = "Version",
Ruleset = new OsuRuleset().RulesetInfo
}
},
RequiredMods =
{
@ -42,7 +39,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
}
});
Room.Type.Value = new GameTypeTimeshift();
Room.Name.Value = "A very awesome room";
Room.Host.Value = new User { Id = 2, Username = "peppy" };
Child = new Header();
}

View File

@ -1,35 +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.
using System;
using System.Collections.Generic;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Screens.Multi.Match.Components;
using osu.Game.Users;
namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneMatchHostInfo : OsuTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(HostInfo)
};
private readonly Bindable<User> host = new Bindable<User>(new User { Username = "SomeHost" });
public TestSceneMatchHostInfo()
{
HostInfo hostInfo;
Child = hostInfo = new HostInfo
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre
};
hostInfo.Host.BindTo(host);
}
}
}

View File

@ -1,78 +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.
using System;
using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Game.Beatmaps;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Multiplayer.RoomStatuses;
using osu.Game.Rulesets;
using osu.Game.Screens.Multi.Match.Components;
namespace osu.Game.Tests.Visual.Multiplayer
{
[TestFixture]
public class TestSceneMatchInfo : MultiplayerTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(Info),
typeof(HeaderButton),
typeof(ReadyButton),
typeof(MatchBeatmapPanel)
};
[BackgroundDependencyLoader]
private void load(RulesetStore rulesets)
{
Add(new Info());
AddStep(@"set name", () => Room.Name.Value = @"Room Name?");
AddStep(@"set availability", () => Room.Availability.Value = RoomAvailability.FriendsOnly);
AddStep(@"set status", () => Room.Status.Value = new RoomStatusPlaying());
AddStep(@"set beatmap", () =>
{
Room.Playlist.Clear();
Room.Playlist.Add(new PlaylistItem
{
Beatmap = new BeatmapInfo
{
StarDifficulty = 2.4,
Ruleset = rulesets.GetRuleset(0),
Metadata = new BeatmapMetadata
{
Title = @"My Song",
Artist = @"VisualTests",
AuthorString = @"osu!lazer",
},
}
});
});
AddStep(@"change name", () => Room.Name.Value = @"Room Name!");
AddStep(@"change availability", () => Room.Availability.Value = RoomAvailability.InviteOnly);
AddStep(@"change status", () => Room.Status.Value = new RoomStatusOpen());
AddStep(@"null beatmap", () => Room.Playlist.Clear());
AddStep(@"change beatmap", () =>
{
Room.Playlist.Clear();
Room.Playlist.Add(new PlaylistItem
{
Beatmap = new BeatmapInfo
{
StarDifficulty = 4.2,
Ruleset = rulesets.GetRuleset(3),
Metadata = new BeatmapMetadata
{
Title = @"Your Song",
Artist = @"Tester",
AuthorString = @"Someone",
},
}
});
});
}
}
}

View File

@ -6,6 +6,7 @@ using Newtonsoft.Json;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Online.API;
using osu.Game.Online.Multiplayer;
using osu.Game.Screens.Multi.Match.Components;
using osu.Game.Users;
using osuTK;
@ -14,11 +15,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneMatchLeaderboard : MultiplayerTestScene
{
protected override bool RequiresAPIAccess => true;
protected override bool UseOnlineAPI => true;
public TestSceneMatchLeaderboard()
{
Room.RoomID.Value = 3;
Room = new Room { RoomID = { Value = 3 } };
Add(new MatchLeaderboard
{

View File

@ -1,52 +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.
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Game.Screens.Multi.Match.Components;
using osu.Game.Users;
namespace osu.Game.Tests.Visual.Multiplayer
{
[TestFixture]
public class TestSceneMatchParticipants : MultiplayerTestScene
{
public TestSceneMatchParticipants()
{
Add(new Participants { RelativeSizeAxes = Axes.Both });
AddStep(@"set max to null", () => Room.MaxParticipants.Value = null);
AddStep(@"set users", () => Room.Participants.Value = new[]
{
new User
{
Username = @"Feppla",
Id = 4271601,
Country = new Country { FlagName = @"SE" },
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c2.jpg",
IsSupporter = true,
},
new User
{
Username = @"Xilver",
Id = 3099689,
Country = new Country { FlagName = @"IL" },
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c2.jpg",
IsSupporter = true,
},
new User
{
Username = @"Wucki",
Id = 5287410,
Country = new Country { FlagName = @"FI" },
CoverUrl = @"https://assets.ppy.sh/user-profile-covers/5287410/5cfeaa9dd41cbce038ecdc9d781396ed4b0108089170bf7f50492ef8eadeb368.jpeg",
IsSupporter = true,
},
});
AddStep(@"set max", () => Room.MaxParticipants.Value = 10);
AddStep(@"clear users", () => Room.Participants.Value = new User[] { });
AddStep(@"set max to null", () => Room.MaxParticipants.Value = null);
}
}
}

Some files were not shown because too many files have changed in this diff Show More