Merge branch 'master' into directoryselector-hidden-toggle

This commit is contained in:
Dean Herbert
2022-10-24 15:33:10 +09:00
committed by GitHub
1450 changed files with 35399 additions and 28765 deletions

View File

@ -10,6 +10,7 @@ using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Track;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Rendering;
using osu.Framework.Graphics.Textures;
using osu.Framework.Screens;
using osu.Framework.Testing;
@ -43,6 +44,9 @@ namespace osu.Game.Tests.Visual.Background
[Resolved]
private OsuConfigManager config { get; set; }
[Resolved]
private IRenderer renderer { get; set; }
[SetUpSteps]
public void SetUpSteps()
{
@ -245,7 +249,7 @@ namespace osu.Game.Tests.Visual.Background
Id = API.LocalUser.Value.Id + 1,
});
private WorkingBeatmap createTestWorkingBeatmapWithUniqueBackground() => new UniqueBackgroundTestWorkingBeatmap(Audio);
private WorkingBeatmap createTestWorkingBeatmapWithUniqueBackground() => new UniqueBackgroundTestWorkingBeatmap(renderer, Audio);
private WorkingBeatmap createTestWorkingBeatmapWithStoryboard() => new TestWorkingBeatmapWithStoryboard(Audio);
private class TestBackgroundScreenDefault : BackgroundScreenDefault
@ -274,12 +278,15 @@ namespace osu.Game.Tests.Visual.Background
private class UniqueBackgroundTestWorkingBeatmap : TestWorkingBeatmap
{
public UniqueBackgroundTestWorkingBeatmap(AudioManager audioManager)
private readonly IRenderer renderer;
public UniqueBackgroundTestWorkingBeatmap(IRenderer renderer, AudioManager audioManager)
: base(new Beatmap(), null, audioManager)
{
this.renderer = renderer;
}
protected override Texture GetBackground() => new Texture(1, 1);
protected override Texture GetBackground() => renderer.CreateTexture(1, 1);
}
private class TestWorkingBeatmapWithStoryboard : TestWorkingBeatmap

View File

@ -10,7 +10,7 @@ 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.Rendering;
using osu.Framework.Graphics.Textures;
using osu.Game.Configuration;
using osu.Game.Graphics.Backgrounds;
@ -28,11 +28,9 @@ namespace osu.Game.Tests.Visual.Background
[Resolved]
private SessionStatics statics { get; set; }
[Cached(typeof(LargeTextureStore))]
private LookupLoggingTextureStore textureStore = new LookupLoggingTextureStore();
private DummyAPIAccess dummyAPI => (DummyAPIAccess)API;
private LookupLoggingTextureStore textureStore;
private SeasonalBackgroundLoader backgroundLoader;
private Container backgroundContainer;
@ -45,15 +43,32 @@ namespace osu.Game.Tests.Visual.Background
"Backgrounds/bg3"
};
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
{
var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
textureStore = new LookupLoggingTextureStore(dependencies.Get<IRenderer>());
dependencies.CacheAs(typeof(LargeTextureStore), textureStore);
return dependencies;
}
[BackgroundDependencyLoader]
private void load(LargeTextureStore wrappedStore)
{
textureStore.AddStore(wrappedStore);
Add(backgroundContainer = new Container
Child = new DependencyProvidingContainer
{
RelativeSizeAxes = Axes.Both
});
CachedDependencies = new (Type, object)[]
{
(typeof(LargeTextureStore), textureStore)
},
Child = backgroundContainer = new Container
{
RelativeSizeAxes = Axes.Both
}
};
}
[SetUp]
@ -156,7 +171,7 @@ namespace osu.Game.Tests.Visual.Background
=> AddStep("create loader", () =>
{
if (backgroundLoader != null)
Remove(backgroundLoader);
Remove(backgroundLoader, true);
Add(backgroundLoader = new SeasonalBackgroundLoader());
});
@ -193,6 +208,11 @@ namespace osu.Game.Tests.Visual.Background
{
public List<string> PerformedLookups { get; } = new List<string>();
public LookupLoggingTextureStore(IRenderer renderer)
: base(renderer)
{
}
public override Texture Get(string name, WrapMode wrapModeS, WrapMode wrapModeT)
{
PerformedLookups.Add(name);

View File

@ -49,7 +49,7 @@ namespace osu.Game.Tests.Visual.Background
private void load(GameHost host, AudioManager audio)
{
Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default));
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default));
Dependencies.Cache(new OsuConfigManager(LocalStorage));
Dependencies.Cache(Realm);
@ -244,7 +244,10 @@ namespace osu.Game.Tests.Visual.Background
public void TestResumeFromPlayer()
{
performFullSetup();
AddStep("Move mouse to Visual Settings", () => InputManager.MoveMouseTo(playerLoader.VisualSettingsPos));
AddStep("Move mouse to Visual Settings location", () => InputManager.MoveMouseTo(playerLoader.ScreenSpaceDrawQuad.TopRight
+ new Vector2(-playerLoader.VisualSettingsPos.ScreenSpaceDrawQuad.Width,
playerLoader.VisualSettingsPos.ScreenSpaceDrawQuad.Height / 2
)));
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));

View File

@ -15,12 +15,14 @@ using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Beatmaps.Drawables.Cards;
using osu.Game.Beatmaps.Drawables.Cards.Buttons;
using osu.Game.Graphics.Containers;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays;
using osuTK;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Beatmaps
{
@ -295,5 +297,22 @@ namespace osu.Game.Tests.Visual.Beatmaps
BeatmapCardNormal firstCard() => this.ChildrenOfType<BeatmapCardNormal>().First();
}
[Test]
public void TestPlayButtonByTouchInput()
{
AddStep("create cards", () => Child = createContent(OverlayColourScheme.Blue, beatmapSetInfo => new BeatmapCardNormal(beatmapSetInfo)));
// mimics touch input
AddStep("touch play button area on first card", () =>
{
InputManager.MoveMouseTo(firstCard().ChildrenOfType<PlayButton>().Single());
InputManager.Click(MouseButton.Left);
});
AddAssert("first card is playing", () => firstCard().ChildrenOfType<PlayButton>().Single().Playing.Value);
BeatmapCardNormal firstCard() => this.ChildrenOfType<BeatmapCardNormal>().First();
}
}
}

View File

@ -9,6 +9,7 @@ using osu.Framework.Graphics;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables.Cards.Buttons;
using osu.Game.Configuration;
using osu.Game.Online;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays;
using osu.Game.Resources.Localisation.Web;
@ -58,6 +59,7 @@ namespace osu.Game.Tests.Visual.Beatmaps
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
State = { Value = DownloadState.NotDownloaded },
Scale = new Vector2(2)
};
});

View File

@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual.Beatmaps
};
});
AddStep("enable dim", () => thumbnail.Dimmed.Value = true);
AddUntilStep("button visible", () => playButton.IsPresent);
AddUntilStep("button visible", () => playButton.Alpha == 1);
AddStep("click button", () =>
{
@ -70,7 +70,7 @@ namespace osu.Game.Tests.Visual.Beatmaps
AddStep("disable dim", () => thumbnail.Dimmed.Value = false);
AddWaitStep("wait some", 3);
AddAssert("button still visible", () => playButton.IsPresent);
AddAssert("button still visible", () => playButton.Alpha == 1);
// The track plays in real-time, so we need to check for progress in increments to avoid timeout.
AddUntilStep("progress > 0.25", () => thumbnail.ChildrenOfType<PlayButton>().Single().Progress.Value > 0.25);
@ -78,7 +78,7 @@ namespace osu.Game.Tests.Visual.Beatmaps
AddUntilStep("progress > 0.75", () => thumbnail.ChildrenOfType<PlayButton>().Single().Progress.Value > 0.75);
AddUntilStep("wait for track to end", () => !playButton.Playing.Value);
AddUntilStep("button hidden", () => !playButton.IsPresent);
AddUntilStep("button hidden", () => playButton.Alpha == 0);
}
private void iconIs(IconUsage usage) => AddUntilStep("icon is correct", () => playButton.ChildrenOfType<SpriteIcon>().Any(icon => icon.Icon.Equals(usage)));

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.
#nullable disable
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
@ -27,38 +25,32 @@ namespace osu.Game.Tests.Visual.Collections
{
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;
private DialogOverlay dialogOverlay = null!;
private BeatmapManager beatmapManager = null!;
private ManageCollectionsDialog dialog = null!;
[BackgroundDependencyLoader]
private void load(GameHost host)
{
Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, rulesets, null, Audio, Resources, host, Beatmap.Default));
Dependencies.Cache(new RealmRulesetStore(Realm));
Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, null, Audio, Resources, host, Beatmap.Default));
Dependencies.Cache(Realm);
beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
base.Content.AddRange(new Drawable[]
{
manager = new CollectionManager(LocalStorage),
Content,
dialogOverlay = new DialogOverlay(),
});
Dependencies.Cache(manager);
Dependencies.CacheAs<IDialogOverlay>(dialogOverlay);
}
[SetUp]
public void SetUp() => Schedule(() =>
{
manager.Collections.Clear();
Realm.Write(r => r.RemoveAll<BeatmapCollection>());
Child = dialog = new ManageCollectionsDialog();
});
@ -78,17 +70,17 @@ namespace osu.Game.Tests.Visual.Collections
[Test]
public void TestLastItemIsPlaceholder()
{
AddAssert("last item is placeholder", () => !manager.Collections.Contains(dialog.ChildrenOfType<DrawableCollectionListItem>().Last().Model));
AddAssert("last item is placeholder", () => !dialog.ChildrenOfType<DrawableCollectionListItem>().Last().Model.IsManaged);
}
[Test]
public void TestAddCollectionExternal()
{
AddStep("add collection", () => manager.Collections.Add(new BeatmapCollection { Name = { Value = "First collection" } }));
AddStep("add collection", () => Realm.Write(r => r.Add(new BeatmapCollection(name: "First collection"))));
assertCollectionCount(1);
assertCollectionName(0, "First collection");
AddStep("add another collection", () => manager.Collections.Add(new BeatmapCollection { Name = { Value = "Second collection" } }));
AddStep("add another collection", () => Realm.Write(r => r.Add(new BeatmapCollection(name: "Second collection"))));
assertCollectionCount(2);
assertCollectionName(1, "Second collection");
}
@ -108,7 +100,7 @@ namespace osu.Game.Tests.Visual.Collections
[Test]
public void TestAddCollectionViaPlaceholder()
{
DrawableCollectionListItem placeholderItem = null;
DrawableCollectionListItem placeholderItem = null!;
AddStep("focus placeholder", () =>
{
@ -116,24 +108,37 @@ namespace osu.Game.Tests.Visual.Collections
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));
assertCollectionCount(0);
AddAssert("last item is placeholder", () => !manager.Collections.Contains(dialog.ChildrenOfType<DrawableCollectionListItem>().Last().Model));
AddStep("change collection name", () =>
{
placeholderItem.ChildrenOfType<TextBox>().First().Text = "test text";
InputManager.Key(Key.Enter);
});
assertCollectionCount(1);
AddAssert("last item is placeholder", () => !dialog.ChildrenOfType<DrawableCollectionListItem>().Last().Model.IsManaged);
}
[Test]
public void TestRemoveCollectionExternal()
{
AddStep("add two collections", () => manager.Collections.AddRange(new[]
{
new BeatmapCollection { Name = { Value = "1" } },
new BeatmapCollection { Name = { Value = "2" } },
}));
BeatmapCollection first = null!;
AddStep("remove first collection", () => manager.Collections.RemoveAt(0));
AddStep("add two collections", () =>
{
Realm.Write(r =>
{
r.Add(new[]
{
first = new BeatmapCollection(name: "1"),
new BeatmapCollection(name: "2"),
});
});
});
AddStep("remove first collection", () => Realm.Write(r => r.Remove(first)));
assertCollectionCount(1);
assertCollectionName(0, "2");
}
@ -143,7 +148,7 @@ namespace osu.Game.Tests.Visual.Collections
{
AddStep("add dropdown", () =>
{
Add(new CollectionFilterDropdown
Add(new CollectionDropdown
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
@ -151,21 +156,27 @@ namespace osu.Game.Tests.Visual.Collections
Width = 0.4f,
});
});
AddStep("add two collections with same name", () => manager.Collections.AddRange(new[]
AddStep("add two collections with same name", () => Realm.Write(r => r.Add(new[]
{
new BeatmapCollection { Name = { Value = "1" } },
new BeatmapCollection { Name = { Value = "1" }, BeatmapHashes = { beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0].MD5Hash } },
}));
new BeatmapCollection(name: "1"),
new BeatmapCollection(name: "1")
{
BeatmapMD5Hashes = { beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0].MD5Hash }
},
})));
}
[Test]
public void TestRemoveCollectionViaButton()
{
AddStep("add two collections", () => manager.Collections.AddRange(new[]
AddStep("add two collections", () => Realm.Write(r => r.Add(new[]
{
new BeatmapCollection { Name = { Value = "1" } },
new BeatmapCollection { Name = { Value = "2" }, BeatmapHashes = { beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0].MD5Hash } },
}));
new BeatmapCollection(name: "1"),
new BeatmapCollection(name: "2")
{
BeatmapMD5Hashes = { beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0].MD5Hash }
},
})));
assertCollectionCount(2);
@ -189,19 +200,24 @@ namespace osu.Game.Tests.Visual.Collections
AddStep("click confirmation", () =>
{
InputManager.MoveMouseTo(dialogOverlay.CurrentDialog.ChildrenOfType<PopupDialogButton>().First());
InputManager.Click(MouseButton.Left);
InputManager.PressButton(MouseButton.Left);
});
assertCollectionCount(0);
AddStep("release mouse button", () => InputManager.ReleaseButton(MouseButton.Left));
}
[Test]
public void TestCollectionNotRemovedWhenDialogCancelled()
{
AddStep("add two collections", () => manager.Collections.AddRange(new[]
AddStep("add collection", () => Realm.Write(r => r.Add(new[]
{
new BeatmapCollection { Name = { Value = "1" }, BeatmapHashes = { beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0].MD5Hash } },
}));
new BeatmapCollection(name: "1")
{
BeatmapMD5Hashes = { beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0].MD5Hash }
},
})));
assertCollectionCount(1);
@ -224,34 +240,67 @@ namespace osu.Game.Tests.Visual.Collections
[Test]
public void TestCollectionRenamedExternal()
{
AddStep("add two collections", () => manager.Collections.AddRange(new[]
BeatmapCollection first = null!;
AddStep("add two collections", () =>
{
new BeatmapCollection { Name = { Value = "1" } },
new BeatmapCollection { Name = { Value = "2" } },
}));
Realm.Write(r =>
{
r.Add(new[]
{
first = new BeatmapCollection(name: "1"),
new BeatmapCollection(name: "2"),
});
});
});
AddStep("change first collection name", () => manager.Collections[0].Name.Value = "First");
assertCollectionName(0, "1");
assertCollectionName(1, "2");
assertCollectionName(0, "First");
AddStep("change first collection name", () => Realm.Write(_ => first.Name = "First"));
// Item will have moved due to alphabetical sorting.
assertCollectionName(0, "2");
assertCollectionName(1, "First");
}
[Test]
public void TestCollectionRenamedOnTextChange()
{
AddStep("add two collections", () => manager.Collections.AddRange(new[]
BeatmapCollection first = null!;
DrawableCollectionListItem firstItem = null!;
AddStep("add two collections", () =>
{
new BeatmapCollection { Name = { Value = "1" } },
new BeatmapCollection { Name = { Value = "2" } },
}));
Realm.Write(r =>
{
r.Add(new[]
{
first = new BeatmapCollection(name: "1"),
new BeatmapCollection(name: "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");
AddStep("focus first collection", () =>
{
InputManager.MoveMouseTo(firstItem = dialog.ChildrenOfType<DrawableCollectionListItem>().First());
InputManager.Click(MouseButton.Left);
});
AddStep("change first collection name", () =>
{
firstItem.ChildrenOfType<TextBox>().First().Text = "First";
InputManager.Key(Key.Enter);
});
AddUntilStep("collection has new name", () => first.Name == "First");
}
private void assertCollectionCount(int count)
=> AddUntilStep($"{count} collections shown", () => dialog.ChildrenOfType<DrawableCollectionListItem>().Count(i => i.IsCreated.Value) == count);
=> AddUntilStep($"{count} collections shown", () => dialog.ChildrenOfType<DrawableCollectionListItem>().Count() == count + 1); // +1 for placeholder
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

@ -190,7 +190,7 @@ namespace osu.Game.Tests.Visual.Components
AddAssert("track stopped", () => !track.IsRunning);
}
private TestPreviewTrackManager.TestPreviewTrack getTrack() => (TestPreviewTrackManager.TestPreviewTrack)trackManager.Get(null);
private TestPreviewTrackManager.TestPreviewTrack getTrack() => (TestPreviewTrackManager.TestPreviewTrack)trackManager.Get(CreateAPIBeatmapSet());
private TestPreviewTrackManager.TestPreviewTrack getOwnedTrack()
{

View File

@ -9,6 +9,7 @@ using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Overlays;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Osu;
@ -34,9 +35,11 @@ namespace osu.Game.Tests.Visual.Editing
{
var beatmap = new OsuBeatmap
{
BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo }
BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo },
};
beatmap.ControlPointInfo.Add(0, new TimingControlPoint());
editorBeatmap = new EditorBeatmap(beatmap, new LegacyBeatmapSkin(beatmap.BeatmapInfo, null));
Beatmap.Value = CreateWorkingBeatmap(editorBeatmap.PlayableBeatmap);
@ -50,7 +53,11 @@ namespace osu.Game.Tests.Visual.Editing
(typeof(IBeatSnapProvider), editorBeatmap),
(typeof(OverlayColourProvider), new OverlayColourProvider(OverlayColourScheme.Green)),
},
Child = new ComposeScreen { State = { Value = Visibility.Visible } },
Children = new Drawable[]
{
editorBeatmap,
new ComposeScreen { State = { Value = Visibility.Visible } },
}
};
});

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;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Extensions;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
using osu.Game.Storyboards;
using osu.Game.Tests.Beatmaps.IO;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Editing
{
public class TestSceneDifficultyDelete : EditorTestScene
{
protected override Ruleset CreateEditorRuleset() => new OsuRuleset();
protected override bool IsolateSavingFromDatabase => false;
[Resolved]
private OsuGameBase game { get; set; } = null!;
[Resolved]
private BeatmapManager beatmaps { get; set; } = null!;
private BeatmapSetInfo importedBeatmapSet = null!;
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null!)
=> beatmaps.GetWorkingBeatmap(importedBeatmapSet.Beatmaps.First());
public override void SetUpSteps()
{
AddStep("import test beatmap", () => importedBeatmapSet = BeatmapImportHelper.LoadOszIntoOsu(game, virtualTrack: true).GetResultSafely());
base.SetUpSteps();
}
[Test]
public void TestDeleteDifficulties()
{
Guid deletedDifficultyID = Guid.Empty;
int countBeforeDeletion = 0;
string beatmapSetHashBefore = string.Empty;
for (int i = 0; i < 12; i++)
{
// Will be reloaded after each deletion.
AddUntilStep("wait for editor to load", () => Editor?.ReadyForUse == true);
AddStep("store selected difficulty", () =>
{
deletedDifficultyID = EditorBeatmap.BeatmapInfo.ID;
countBeforeDeletion = Beatmap.Value.BeatmapSetInfo.Beatmaps.Count;
beatmapSetHashBefore = Beatmap.Value.BeatmapSetInfo.Hash;
});
AddStep("click File", () => this.ChildrenOfType<DrawableOsuMenuItem>().First().TriggerClick());
if (i == 11)
{
// last difficulty shouldn't be able to be deleted.
AddAssert("Delete menu item disabled", () => getDeleteMenuItem().Item.Action.Disabled);
}
else
{
AddStep("click delete", () => getDeleteMenuItem().TriggerClick());
AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog != null);
AddStep("confirm", () => InputManager.Key(Key.Number1));
AddAssert($"difficulty {i} is deleted", () => Beatmap.Value.BeatmapSetInfo.Beatmaps.Select(b => b.ID), () => Does.Not.Contain(deletedDifficultyID));
AddAssert("count decreased by one", () => Beatmap.Value.BeatmapSetInfo.Beatmaps.Count, () => Is.EqualTo(countBeforeDeletion - 1));
AddAssert("set hash changed", () => Beatmap.Value.BeatmapSetInfo.Hash, () => Is.Not.EqualTo(beatmapSetHashBefore));
}
}
}
private DrawableOsuMenuItem getDeleteMenuItem() => this.ChildrenOfType<DrawableOsuMenuItem>()
.Single(item => item.ChildrenOfType<SpriteText>().Any(text => text.Text.ToString().StartsWith("Delete", StringComparison.Ordinal)));
}
}

View File

@ -6,6 +6,7 @@ using System.IO;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio.Track;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics;
using osu.Framework.Screens;
@ -55,8 +56,10 @@ namespace osu.Game.Tests.Visual.Editing
[Test]
public void TestCreateNewBeatmap()
{
AddAssert("status is none", () => EditorBeatmap.BeatmapInfo.Status == BeatmapOnlineStatus.None);
AddStep("save beatmap", () => Editor.Save());
AddAssert("new beatmap in database", () => beatmapManager.QueryBeatmapSet(s => s.ID == currentBeatmapSetID)?.Value.DeletePending == false);
AddAssert("status is modified", () => EditorBeatmap.BeatmapInfo.Status == BeatmapOnlineStatus.LocallyModified);
}
[Test]
@ -103,6 +106,8 @@ namespace osu.Game.Tests.Visual.Editing
*/
public void TestAddAudioTrack()
{
AddAssert("track is virtual", () => Beatmap.Value.Track is TrackVirtual);
AddAssert("switch track to real track", () =>
{
var setup = Editor.ChildrenOfType<SetupScreen>().First();
@ -131,7 +136,22 @@ namespace osu.Game.Tests.Visual.Editing
}
});
AddAssert("track is not virtual", () => Beatmap.Value.Track is not TrackVirtual);
AddAssert("track length changed", () => Beatmap.Value.Track.Length > 60000);
AddStep("test play", () => Editor.TestGameplay());
AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog != null);
AddStep("confirm save", () => InputManager.Key(Key.Number1));
AddUntilStep("wait for return to editor", () => Editor.IsCurrentScreen());
AddAssert("track is still not virtual", () => Beatmap.Value.Track is not TrackVirtual);
AddAssert("track length correct", () => Beatmap.Value.Track.Length > 60000);
AddUntilStep("track not playing", () => !EditorClock.IsRunning);
AddStep("play track", () => InputManager.Key(Key.Space));
AddUntilStep("wait for track playing", () => EditorClock.IsRunning);
}
[Test]
@ -190,6 +210,8 @@ namespace osu.Game.Tests.Visual.Editing
});
AddAssert("created difficulty has no objects", () => EditorBeatmap.HitObjects.Count == 0);
AddAssert("status is modified", () => EditorBeatmap.BeatmapInfo.Status == BeatmapOnlineStatus.LocallyModified);
AddStep("set unique difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = secondDifficultyName);
AddStep("save beatmap", () => Editor.Save());
AddAssert("new beatmap persisted", () =>
@ -200,7 +222,7 @@ namespace osu.Game.Tests.Visual.Editing
return beatmap != null
&& beatmap.DifficultyName == secondDifficultyName
&& set != null
&& set.PerformRead(s => s.Beatmaps.Count == 2 && s.Beatmaps.Any(b => b.DifficultyName == secondDifficultyName));
&& set.PerformRead(s => s.Beatmaps.Count == 2 && s.Beatmaps.Any(b => b.DifficultyName == secondDifficultyName) && s.Beatmaps.All(b => s.Status == BeatmapOnlineStatus.LocallyModified));
});
}
@ -276,7 +298,7 @@ namespace osu.Game.Tests.Visual.Editing
AddAssert("approach rate correctly copied", () => EditorBeatmap.Difficulty.ApproachRate == 4);
AddAssert("combo colours correctly copied", () => EditorBeatmap.BeatmapSkin.AsNonNull().ComboColours.Count == 2);
AddAssert("status not copied", () => EditorBeatmap.BeatmapInfo.Status == BeatmapOnlineStatus.None);
AddAssert("status is modified", () => EditorBeatmap.BeatmapInfo.Status == BeatmapOnlineStatus.LocallyModified);
AddAssert("online ID not copied", () => EditorBeatmap.BeatmapInfo.OnlineID == -1);
AddStep("save beatmap", () => Editor.Save());

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.
#nullable disable
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
@ -57,51 +55,51 @@ namespace osu.Game.Tests.Visual.Editing
[Test]
public void TestStopAtTrackEnd()
{
AddStep("reset clock", () => Clock.Seek(0));
AddStep("reset clock", () => EditorClock.Seek(0));
AddStep("start clock", Clock.Start);
AddAssert("clock running", () => Clock.IsRunning);
AddStep("start clock", () => EditorClock.Start());
AddAssert("clock running", () => EditorClock.IsRunning);
AddStep("seek near end", () => Clock.Seek(Clock.TrackLength - 250));
AddUntilStep("clock stops", () => !Clock.IsRunning);
AddStep("seek near end", () => EditorClock.Seek(EditorClock.TrackLength - 250));
AddUntilStep("clock stops", () => !EditorClock.IsRunning);
AddAssert("clock stopped at end", () => Clock.CurrentTime == Clock.TrackLength);
AddUntilStep("clock stopped at end", () => EditorClock.CurrentTime - EditorClock.TotalAppliedOffset, () => Is.EqualTo(EditorClock.TrackLength));
AddStep("start clock again", Clock.Start);
AddAssert("clock looped to start", () => Clock.IsRunning && Clock.CurrentTime < 500);
AddStep("start clock again", () => EditorClock.Start());
AddAssert("clock looped to start", () => EditorClock.IsRunning && EditorClock.CurrentTime < 500);
}
[Test]
public void TestWrapWhenStoppedAtTrackEnd()
{
AddStep("reset clock", () => Clock.Seek(0));
AddStep("reset clock", () => EditorClock.Seek(0));
AddStep("stop clock", Clock.Stop);
AddAssert("clock stopped", () => !Clock.IsRunning);
AddStep("stop clock", () => EditorClock.Stop());
AddAssert("clock stopped", () => !EditorClock.IsRunning);
AddStep("seek exactly to end", () => Clock.Seek(Clock.TrackLength));
AddAssert("clock stopped at end", () => Clock.CurrentTime == Clock.TrackLength);
AddStep("seek exactly to end", () => EditorClock.Seek(EditorClock.TrackLength));
AddAssert("clock stopped at end", () => EditorClock.CurrentTime, () => Is.EqualTo(EditorClock.TrackLength));
AddStep("start clock again", Clock.Start);
AddAssert("clock looped to start", () => Clock.IsRunning && Clock.CurrentTime < 500);
AddStep("start clock again", () => EditorClock.Start());
AddAssert("clock looped to start", () => EditorClock.IsRunning && EditorClock.CurrentTime < 500);
}
[Test]
public void TestClampWhenSeekOutsideBeatmapBounds()
{
AddStep("stop clock", Clock.Stop);
AddStep("stop clock", () => EditorClock.Stop());
AddStep("seek before start time", () => Clock.Seek(-1000));
AddAssert("time is clamped to 0", () => Clock.CurrentTime == 0);
AddStep("seek before start time", () => EditorClock.Seek(-1000));
AddAssert("time is clamped to 0", () => EditorClock.CurrentTime, () => Is.EqualTo(0));
AddStep("seek beyond track length", () => Clock.Seek(Clock.TrackLength + 1000));
AddAssert("time is clamped to track length", () => Clock.CurrentTime == Clock.TrackLength);
AddStep("seek beyond track length", () => EditorClock.Seek(EditorClock.TrackLength + 1000));
AddAssert("time is clamped to track length", () => EditorClock.CurrentTime, () => Is.EqualTo(EditorClock.TrackLength));
AddStep("seek smoothly before start time", () => Clock.SeekSmoothlyTo(-1000));
AddAssert("time is clamped to 0", () => Clock.CurrentTime == 0);
AddStep("seek smoothly before start time", () => EditorClock.SeekSmoothlyTo(-1000));
AddUntilStep("time is clamped to 0", () => EditorClock.CurrentTime, () => Is.EqualTo(0));
AddStep("seek smoothly beyond track length", () => Clock.SeekSmoothlyTo(Clock.TrackLength + 1000));
AddAssert("time is clamped to track length", () => Clock.CurrentTime == Clock.TrackLength);
AddStep("seek smoothly beyond track length", () => EditorClock.SeekSmoothlyTo(EditorClock.TrackLength + 1000));
AddUntilStep("time is clamped to track length", () => EditorClock.CurrentTime, () => Is.EqualTo(EditorClock.TrackLength));
}
protected override void Dispose(bool isDisposing)

View File

@ -4,8 +4,10 @@
#nullable disable
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Game.Overlays;
using osu.Game.Screens.Edit.Components.RadioButtons;
namespace osu.Game.Tests.Visual.Editing
@ -13,6 +15,9 @@ namespace osu.Game.Tests.Visual.Editing
[TestFixture]
public class TestSceneEditorComposeRadioButtons : OsuTestScene
{
[Cached]
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine);
public TestSceneEditorComposeRadioButtons()
{
EditorRadioButtonCollection collection;

View File

@ -6,11 +6,13 @@
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Overlays;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Compose.Components.Timeline;
using osu.Game.Screens.Select;
@ -23,7 +25,9 @@ namespace osu.Game.Tests.Visual.Editing
[Test]
public void TestCantExitWithoutSaving()
{
AddUntilStep("Wait for dialog overlay load", () => ((Drawable)Game.Dependencies.Get<IDialogOverlay>()).IsLoaded);
AddRepeatStep("Exit", () => InputManager.Key(Key.Escape), 10);
AddAssert("Sample playback disabled", () => Editor.SamplePlaybackDisabled.Value);
AddAssert("Editor is still active screen", () => Game.ScreenStack.CurrentScreen is Editor);
}

View File

@ -28,6 +28,11 @@ namespace osu.Game.Tests.Visual.Editing
{
base.LoadComplete();
Child = new TimingPointVisualiser(Beatmap.Value.Beatmap, 5000) { Clock = EditorClock };
}
protected override Beatmap CreateEditorClockBeatmap()
{
var testBeatmap = new Beatmap
{
ControlPointInfo = new ControlPointInfo(),
@ -45,9 +50,7 @@ namespace osu.Game.Tests.Visual.Editing
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 };
return testBeatmap;
}
/// <summary>
@ -59,18 +62,18 @@ namespace osu.Game.Tests.Visual.Editing
reset();
// Forwards
AddStep("Seek(0)", () => Clock.Seek(0));
AddAssert("Time = 0", () => Clock.CurrentTime == 0);
AddStep("Seek(33)", () => Clock.Seek(33));
AddAssert("Time = 33", () => Clock.CurrentTime == 33);
AddStep("Seek(89)", () => Clock.Seek(89));
AddAssert("Time = 89", () => Clock.CurrentTime == 89);
AddStep("Seek(0)", () => EditorClock.Seek(0));
checkTime(0);
AddStep("Seek(33)", () => EditorClock.Seek(33));
checkTime(33);
AddStep("Seek(89)", () => EditorClock.Seek(89));
checkTime(89);
// Backwards
AddStep("Seek(25)", () => Clock.Seek(25));
AddAssert("Time = 25", () => Clock.CurrentTime == 25);
AddStep("Seek(0)", () => Clock.Seek(0));
AddAssert("Time = 0", () => Clock.CurrentTime == 0);
AddStep("Seek(25)", () => EditorClock.Seek(25));
checkTime(25);
AddStep("Seek(0)", () => EditorClock.Seek(0));
checkTime(0);
}
/// <summary>
@ -82,20 +85,20 @@ namespace osu.Game.Tests.Visual.Editing
{
reset();
AddStep("Seek(0), Snap", () => Clock.SeekSnapped(0));
AddAssert("Time = 0", () => Clock.CurrentTime == 0);
AddStep("Seek(50), Snap", () => Clock.SeekSnapped(50));
AddAssert("Time = 50", () => Clock.CurrentTime == 50);
AddStep("Seek(100), Snap", () => Clock.SeekSnapped(100));
AddAssert("Time = 100", () => Clock.CurrentTime == 100);
AddStep("Seek(175), Snap", () => Clock.SeekSnapped(175));
AddAssert("Time = 175", () => Clock.CurrentTime == 175);
AddStep("Seek(350), Snap", () => Clock.SeekSnapped(350));
AddAssert("Time = 350", () => Clock.CurrentTime == 350);
AddStep("Seek(400), Snap", () => Clock.SeekSnapped(400));
AddAssert("Time = 400", () => Clock.CurrentTime == 400);
AddStep("Seek(450), Snap", () => Clock.SeekSnapped(450));
AddAssert("Time = 450", () => Clock.CurrentTime == 450);
AddStep("Seek(0), Snap", () => EditorClock.SeekSnapped(0));
checkTime(0);
AddStep("Seek(50), Snap", () => EditorClock.SeekSnapped(50));
checkTime(50);
AddStep("Seek(100), Snap", () => EditorClock.SeekSnapped(100));
checkTime(100);
AddStep("Seek(175), Snap", () => EditorClock.SeekSnapped(175));
checkTime(175);
AddStep("Seek(350), Snap", () => EditorClock.SeekSnapped(350));
checkTime(350);
AddStep("Seek(400), Snap", () => EditorClock.SeekSnapped(400));
checkTime(400);
AddStep("Seek(450), Snap", () => EditorClock.SeekSnapped(450));
checkTime(450);
}
/// <summary>
@ -107,18 +110,18 @@ namespace osu.Game.Tests.Visual.Editing
{
reset();
AddStep("Seek(24), Snap", () => Clock.SeekSnapped(24));
AddAssert("Time = 0", () => Clock.CurrentTime == 0);
AddStep("Seek(26), Snap", () => Clock.SeekSnapped(26));
AddAssert("Time = 50", () => Clock.CurrentTime == 50);
AddStep("Seek(150), Snap", () => Clock.SeekSnapped(150));
AddAssert("Time = 100", () => Clock.CurrentTime == 100);
AddStep("Seek(170), Snap", () => Clock.SeekSnapped(170));
AddAssert("Time = 175", () => Clock.CurrentTime == 175);
AddStep("Seek(274), Snap", () => Clock.SeekSnapped(274));
AddAssert("Time = 175", () => Clock.CurrentTime == 175);
AddStep("Seek(276), Snap", () => Clock.SeekSnapped(276));
AddAssert("Time = 350", () => Clock.CurrentTime == 350);
AddStep("Seek(24), Snap", () => EditorClock.SeekSnapped(24));
checkTime(0);
AddStep("Seek(26), Snap", () => EditorClock.SeekSnapped(26));
checkTime(50);
AddStep("Seek(150), Snap", () => EditorClock.SeekSnapped(150));
checkTime(100);
AddStep("Seek(170), Snap", () => EditorClock.SeekSnapped(170));
checkTime(175);
AddStep("Seek(274), Snap", () => EditorClock.SeekSnapped(274));
checkTime(175);
AddStep("Seek(276), Snap", () => EditorClock.SeekSnapped(276));
checkTime(350);
}
/// <summary>
@ -129,16 +132,16 @@ namespace osu.Game.Tests.Visual.Editing
{
reset();
AddStep("SeekForward", () => Clock.SeekForward());
AddAssert("Time = 50", () => Clock.CurrentTime == 50);
AddStep("SeekForward", () => Clock.SeekForward());
AddAssert("Time = 100", () => Clock.CurrentTime == 100);
AddStep("SeekForward", () => Clock.SeekForward());
AddAssert("Time = 200", () => Clock.CurrentTime == 200);
AddStep("SeekForward", () => Clock.SeekForward());
AddAssert("Time = 400", () => Clock.CurrentTime == 400);
AddStep("SeekForward", () => Clock.SeekForward());
AddAssert("Time = 450", () => Clock.CurrentTime == 450);
AddStep("SeekForward", () => EditorClock.SeekForward());
checkTime(50);
AddStep("SeekForward", () => EditorClock.SeekForward());
checkTime(100);
AddStep("SeekForward", () => EditorClock.SeekForward());
checkTime(200);
AddStep("SeekForward", () => EditorClock.SeekForward());
checkTime(400);
AddStep("SeekForward", () => EditorClock.SeekForward());
checkTime(450);
}
/// <summary>
@ -149,18 +152,18 @@ namespace osu.Game.Tests.Visual.Editing
{
reset();
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
AddAssert("Time = 50", () => Clock.CurrentTime == 50);
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
AddAssert("Time = 100", () => Clock.CurrentTime == 100);
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
AddAssert("Time = 175", () => Clock.CurrentTime == 175);
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
AddAssert("Time = 350", () => Clock.CurrentTime == 350);
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
AddAssert("Time = 400", () => Clock.CurrentTime == 400);
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
AddAssert("Time = 450", () => Clock.CurrentTime == 450);
AddStep("SeekForward, Snap", () => EditorClock.SeekForward(true));
checkTime(50);
AddStep("SeekForward, Snap", () => EditorClock.SeekForward(true));
checkTime(100);
AddStep("SeekForward, Snap", () => EditorClock.SeekForward(true));
checkTime(175);
AddStep("SeekForward, Snap", () => EditorClock.SeekForward(true));
checkTime(350);
AddStep("SeekForward, Snap", () => EditorClock.SeekForward(true));
checkTime(400);
AddStep("SeekForward, Snap", () => EditorClock.SeekForward(true));
checkTime(450);
}
/// <summary>
@ -172,30 +175,31 @@ namespace osu.Game.Tests.Visual.Editing
{
reset();
AddStep("Seek(49)", () => Clock.Seek(49));
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
AddAssert("Time = 50", () => Clock.CurrentTime == 50);
AddStep("Seek(49.999)", () => Clock.Seek(49.999));
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
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 == 150);
AddStep("Seek(174)", () => Clock.Seek(174));
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
AddAssert("Time = 175", () => Clock.CurrentTime == 175);
AddStep("Seek(349)", () => Clock.Seek(349));
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
AddAssert("Time = 350", () => Clock.CurrentTime == 350);
AddStep("Seek(399)", () => Clock.Seek(399));
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
AddAssert("Time = 400", () => Clock.CurrentTime == 400);
AddStep("Seek(449)", () => Clock.Seek(449));
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
AddAssert("Time = 450", () => Clock.CurrentTime == 450);
AddStep("Seek(49)", () => EditorClock.Seek(49));
checkTime(49);
AddStep("SeekForward, Snap", () => EditorClock.SeekForward(true));
checkTime(50);
AddStep("Seek(49.999)", () => EditorClock.Seek(49.999));
AddStep("SeekForward, Snap", () => EditorClock.SeekForward(true));
checkTime(100);
AddStep("Seek(99)", () => EditorClock.Seek(99));
AddStep("SeekForward, Snap", () => EditorClock.SeekForward(true));
checkTime(100);
AddStep("Seek(99.999)", () => EditorClock.Seek(99.999));
AddStep("SeekForward, Snap", () => EditorClock.SeekForward(true));
checkTime(150);
AddStep("Seek(174)", () => EditorClock.Seek(174));
AddStep("SeekForward, Snap", () => EditorClock.SeekForward(true));
checkTime(175);
AddStep("Seek(349)", () => EditorClock.Seek(349));
AddStep("SeekForward, Snap", () => EditorClock.SeekForward(true));
checkTime(350);
AddStep("Seek(399)", () => EditorClock.Seek(399));
AddStep("SeekForward, Snap", () => EditorClock.SeekForward(true));
checkTime(400);
AddStep("Seek(449)", () => EditorClock.Seek(449));
AddStep("SeekForward, Snap", () => EditorClock.SeekForward(true));
checkTime(450);
}
/// <summary>
@ -206,17 +210,18 @@ namespace osu.Game.Tests.Visual.Editing
{
reset();
AddStep("Seek(450)", () => Clock.Seek(450));
AddStep("SeekBackward", () => Clock.SeekBackward());
AddAssert("Time = 400", () => Clock.CurrentTime == 400);
AddStep("SeekBackward", () => Clock.SeekBackward());
AddAssert("Time = 350", () => Clock.CurrentTime == 350);
AddStep("SeekBackward", () => Clock.SeekBackward());
AddAssert("Time = 150", () => Clock.CurrentTime == 150);
AddStep("SeekBackward", () => Clock.SeekBackward());
AddAssert("Time = 50", () => Clock.CurrentTime == 50);
AddStep("SeekBackward", () => Clock.SeekBackward());
AddAssert("Time = 0", () => Clock.CurrentTime == 0);
AddStep("Seek(450)", () => EditorClock.Seek(450));
checkTime(450);
AddStep("SeekBackward", () => EditorClock.SeekBackward());
checkTime(400);
AddStep("SeekBackward", () => EditorClock.SeekBackward());
checkTime(350);
AddStep("SeekBackward", () => EditorClock.SeekBackward());
checkTime(150);
AddStep("SeekBackward", () => EditorClock.SeekBackward());
checkTime(50);
AddStep("SeekBackward", () => EditorClock.SeekBackward());
checkTime(0);
}
/// <summary>
@ -227,19 +232,20 @@ namespace osu.Game.Tests.Visual.Editing
{
reset();
AddStep("Seek(450)", () => Clock.Seek(450));
AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true));
AddAssert("Time = 400", () => Clock.CurrentTime == 400);
AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true));
AddAssert("Time = 350", () => Clock.CurrentTime == 350);
AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true));
AddAssert("Time = 175", () => Clock.CurrentTime == 175);
AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true));
AddAssert("Time = 100", () => Clock.CurrentTime == 100);
AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true));
AddAssert("Time = 50", () => Clock.CurrentTime == 50);
AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true));
AddAssert("Time = 0", () => Clock.CurrentTime == 0);
AddStep("Seek(450)", () => EditorClock.Seek(450));
checkTime(450);
AddStep("SeekBackward, Snap", () => EditorClock.SeekBackward(true));
checkTime(400);
AddStep("SeekBackward, Snap", () => EditorClock.SeekBackward(true));
checkTime(350);
AddStep("SeekBackward, Snap", () => EditorClock.SeekBackward(true));
checkTime(175);
AddStep("SeekBackward, Snap", () => EditorClock.SeekBackward(true));
checkTime(100);
AddStep("SeekBackward, Snap", () => EditorClock.SeekBackward(true));
checkTime(50);
AddStep("SeekBackward, Snap", () => EditorClock.SeekBackward(true));
checkTime(0);
}
/// <summary>
@ -251,18 +257,19 @@ namespace osu.Game.Tests.Visual.Editing
{
reset();
AddStep("Seek(451)", () => Clock.Seek(451));
AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true));
AddAssert("Time = 450", () => Clock.CurrentTime == 450);
AddStep("Seek(450.999)", () => Clock.Seek(450.999));
AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true));
AddAssert("Time = 450", () => Clock.CurrentTime == 450);
AddStep("Seek(401)", () => Clock.Seek(401));
AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true));
AddAssert("Time = 400", () => Clock.CurrentTime == 400);
AddStep("Seek(401.999)", () => Clock.Seek(401.999));
AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true));
AddAssert("Time = 400", () => Clock.CurrentTime == 400);
AddStep("Seek(451)", () => EditorClock.Seek(451));
checkTime(451);
AddStep("SeekBackward, Snap", () => EditorClock.SeekBackward(true));
checkTime(450);
AddStep("Seek(450.999)", () => EditorClock.Seek(450.999));
AddStep("SeekBackward, Snap", () => EditorClock.SeekBackward(true));
checkTime(450);
AddStep("Seek(401)", () => EditorClock.Seek(401));
AddStep("SeekBackward, Snap", () => EditorClock.SeekBackward(true));
checkTime(400);
AddStep("Seek(401.999)", () => EditorClock.Seek(401.999));
AddStep("SeekBackward, Snap", () => EditorClock.SeekBackward(true));
checkTime(400);
}
/// <summary>
@ -275,34 +282,37 @@ namespace osu.Game.Tests.Visual.Editing
double lastTime = 0;
AddStep("Seek(0)", () => Clock.Seek(0));
AddStep("Seek(0)", () => EditorClock.Seek(0));
checkTime(0);
for (int i = 0; i < 9; i++)
{
AddStep("SeekForward, Snap", () =>
{
lastTime = Clock.CurrentTime;
Clock.SeekForward(true);
lastTime = EditorClock.CurrentTime;
EditorClock.SeekForward(true);
});
AddAssert("Time > lastTime", () => Clock.CurrentTime > lastTime);
AddAssert("Time > lastTime", () => EditorClock.CurrentTime > lastTime);
}
for (int i = 0; i < 9; i++)
{
AddStep("SeekBackward, Snap", () =>
{
lastTime = Clock.CurrentTime;
Clock.SeekBackward(true);
lastTime = EditorClock.CurrentTime;
EditorClock.SeekBackward(true);
});
AddAssert("Time < lastTime", () => Clock.CurrentTime < lastTime);
AddAssert("Time < lastTime", () => EditorClock.CurrentTime < lastTime);
}
AddAssert("Time = 0", () => Clock.CurrentTime == 0);
checkTime(0);
}
private void checkTime(double expectedTime) => AddUntilStep($"Current time is {expectedTime}", () => EditorClock.CurrentTime, () => Is.EqualTo(expectedTime));
private void reset()
{
AddStep("Reset", () => Clock.Seek(0));
AddStep("Reset", () => EditorClock.Seek(0));
}
private class TimingPointVisualiser : CompositeDrawable

View File

@ -4,7 +4,6 @@
#nullable disable
using NUnit.Framework;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets;
@ -120,7 +119,7 @@ namespace osu.Game.Tests.Visual.Editing
private void pressAndCheckTime(Key key, double expectedTime)
{
AddStep($"press {key}", () => InputManager.Key(key));
AddUntilStep($"time is {expectedTime}", () => Precision.AlmostEquals(expectedTime, EditorClock.CurrentTime, 1));
AddUntilStep($"time is {expectedTime}", () => EditorClock.CurrentTime, () => Is.EqualTo(expectedTime).Within(1));
}
}
}

View File

@ -148,10 +148,6 @@ namespace osu.Game.Tests.Visual.Editing
});
AddAssert("no circles placed", () => editorBeatmap.HitObjects.Count == 0);
AddStep("place circle", () => InputManager.Click(MouseButton.Left));
AddAssert("circle placed", () => editorBeatmap.HitObjects.Count == 1);
}
[Test]

View File

@ -1,13 +1,9 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Beatmaps;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Components;
using osuTK;
@ -19,19 +15,12 @@ namespace osu.Game.Tests.Visual.Editing
[BackgroundDependencyLoader]
private void load()
{
var clock = new EditorClock { IsCoupled = false };
Dependencies.CacheAs(clock);
var playback = new PlaybackControl
Child = new PlaybackControl
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(200, 100)
};
Beatmap.Value = CreateWorkingBeatmap(new Beatmap());
Child = playback;
}
}
}

View File

@ -0,0 +1,75 @@
// 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.Osu;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Tests.Beatmaps;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Editing
{
public class TestSceneSelectionBlueprintDeselection : EditorTestScene
{
protected override Ruleset CreateEditorRuleset() => new OsuRuleset();
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false);
[Test]
public void TestSingleDeleteAtSameTime()
{
HitCircle? circle1 = null;
AddStep("add two circles at the same time", () =>
{
EditorClock.Seek(0);
circle1 = new HitCircle();
var circle2 = new HitCircle();
EditorBeatmap.Add(circle1);
EditorBeatmap.Add(circle2);
EditorBeatmap.SelectedHitObjects.Add(circle1);
EditorBeatmap.SelectedHitObjects.Add(circle2);
});
AddStep("delete the first circle", () => EditorBeatmap.Remove(circle1));
AddAssert("one hitobject remains", () => EditorBeatmap.HitObjects.Count == 1);
AddAssert("one hitobject selected", () => EditorBeatmap.SelectedHitObjects.Count == 1);
}
[Test]
public void TestBigStackDeleteAtSameTime()
{
AddStep("add 20 circles at the same time", () =>
{
EditorClock.Seek(0);
for (int i = 0; i < 20; i++)
{
EditorBeatmap.Add(new HitCircle());
}
});
AddStep("select half of the circles", () =>
{
foreach (var hitObject in EditorBeatmap.HitObjects.SkipLast(10).Reverse())
{
EditorBeatmap.SelectedHitObjects.Add(hitObject);
}
});
AddStep("delete all selected circles", () =>
{
InputManager.PressKey(Key.Delete);
InputManager.ReleaseKey(Key.Delete);
});
AddAssert("10 hitobjects remain", () => EditorBeatmap.HitObjects.Count == 10);
AddAssert("no hitobjects selected", () => EditorBeatmap.SelectedHitObjects.Count == 0);
}
}
}

View File

@ -113,7 +113,7 @@ namespace osu.Game.Tests.Visual.Editing
.TriggerClick();
});
AddUntilStep("wait for track playing", () => Clock.IsRunning);
AddUntilStep("wait for track playing", () => EditorClock.IsRunning);
AddStep("click reset button", () =>
{
@ -122,7 +122,7 @@ namespace osu.Game.Tests.Visual.Editing
.TriggerClick();
});
AddUntilStep("wait for track stopped", () => !Clock.IsRunning);
AddUntilStep("wait for track stopped", () => !EditorClock.IsRunning);
}
protected override void Dispose(bool isDisposing)

View File

@ -17,7 +17,7 @@ namespace osu.Game.Tests.Visual.Editing
protected override void LoadComplete()
{
base.LoadComplete();
Clock.Seek(10000);
EditorClock.Seek(10000);
}
}
}

View File

@ -29,16 +29,18 @@ namespace osu.Game.Tests.Visual.Editing
private TimelineBlueprintContainer blueprintContainer
=> Editor.ChildrenOfType<TimelineBlueprintContainer>().First();
private Vector2 getPosition(HitObject hitObject) =>
blueprintContainer.SelectionBlueprints.First(s => s.Item == hitObject).ScreenSpaceDrawQuad.Centre;
private Vector2 getMiddlePosition(HitObject hitObject1, HitObject hitObject2) =>
(getPosition(hitObject1) + getPosition(hitObject2)) / 2;
private void moveMouseToObject(Func<HitObject> targetFunc)
{
AddStep("move mouse to object", () =>
{
var pos = blueprintContainer.SelectionBlueprints
.First(s => s.Item == targetFunc())
.ChildrenOfType<TimelineHitObjectBlueprint>()
.First().ScreenSpaceDrawQuad.Centre;
InputManager.MoveMouseTo(pos);
var hitObject = targetFunc();
InputManager.MoveMouseTo(getPosition(hitObject));
});
}
@ -262,6 +264,56 @@ namespace osu.Game.Tests.Visual.Editing
AddStep("release shift", () => InputManager.ReleaseKey(Key.ShiftLeft));
}
[Test]
public void TestBasicDragSelection()
{
var addedObjects = new[]
{
new HitCircle { StartTime = 0 },
new HitCircle { StartTime = 500, Position = new Vector2(100) },
new HitCircle { StartTime = 1000, Position = new Vector2(200) },
new HitCircle { StartTime = 1500, Position = new Vector2(300) },
};
AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects));
AddStep("move mouse", () => InputManager.MoveMouseTo(getMiddlePosition(addedObjects[0], addedObjects[1])));
AddStep("mouse down", () => InputManager.PressButton(MouseButton.Left));
AddStep("drag to select", () => InputManager.MoveMouseTo(getMiddlePosition(addedObjects[2], addedObjects[3])));
assertSelectionIs(new[] { addedObjects[1], addedObjects[2] });
AddStep("drag to deselect", () => InputManager.MoveMouseTo(getMiddlePosition(addedObjects[1], addedObjects[2])));
assertSelectionIs(new[] { addedObjects[1] });
AddStep("mouse up", () => InputManager.ReleaseButton(MouseButton.Left));
assertSelectionIs(new[] { addedObjects[1] });
}
[Test]
public void TestFastDragSelection()
{
var addedObjects = new[]
{
new HitCircle { StartTime = 0 },
new HitCircle { StartTime = 500 },
new HitCircle { StartTime = 20000, Position = new Vector2(100) },
new HitCircle { StartTime = 31000, Position = new Vector2(200) },
new HitCircle { StartTime = 60000, Position = new Vector2(300) },
};
AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects));
AddStep("move mouse", () => InputManager.MoveMouseTo(getMiddlePosition(addedObjects[0], addedObjects[1])));
AddStep("mouse down", () => InputManager.PressButton(MouseButton.Left));
AddStep("start drag", () => InputManager.MoveMouseTo(getPosition(addedObjects[1])));
AddStep("jump editor clock", () => EditorClock.Seek(30000));
AddStep("jump editor clock", () => EditorClock.Seek(60000));
AddStep("end drag", () => InputManager.ReleaseButton(MouseButton.Left));
assertSelectionIs(addedObjects.Skip(1));
AddAssert("all blueprints are present", () => blueprintContainer.SelectionBlueprints.Count == EditorBeatmap.SelectedHitObjects.Count);
}
private void assertSelectionIs(IEnumerable<HitObject> hitObjects)
=> AddAssert("correct hitobjects selected", () => EditorBeatmap.SelectedHitObjects.OrderBy(h => h.StartTime).SequenceEqual(hitObjects));
}

View File

@ -5,7 +5,6 @@
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Utils;
namespace osu.Game.Tests.Visual.Editing
{
@ -14,16 +13,6 @@ namespace osu.Game.Tests.Visual.Editing
public override Drawable CreateTestComponent() => Empty();
[Test]
[FlakyTest]
/*
* Fail rate around 0.3%
*
* TearDown : osu.Framework.Testing.Drawables.Steps.AssertButton+TracedException : range halved
* --TearDown
* at osu.Framework.Threading.ScheduledDelegate.RunTaskInternal()
* at osu.Framework.Threading.Scheduler.Update()
* at osu.Framework.Graphics.Drawable.UpdateSubTree()
*/
public void TestVisibleRangeUpdatesOnZoomChange()
{
double initialVisibleRange = 0;
@ -32,12 +21,12 @@ namespace osu.Game.Tests.Visual.Editing
AddStep("get initial range", () => initialVisibleRange = TimelineArea.Timeline.VisibleRange);
AddStep("scale zoom", () => TimelineArea.Timeline.Zoom = 200);
AddAssert("range halved", () => Precision.AlmostEquals(TimelineArea.Timeline.VisibleRange, initialVisibleRange / 2, 1));
AddStep("range halved", () => Assert.That(TimelineArea.Timeline.VisibleRange, Is.EqualTo(initialVisibleRange / 2).Within(1)));
AddStep("descale zoom", () => TimelineArea.Timeline.Zoom = 50);
AddAssert("range doubled", () => Precision.AlmostEquals(TimelineArea.Timeline.VisibleRange, initialVisibleRange * 2, 1));
AddStep("range doubled", () => Assert.That(TimelineArea.Timeline.VisibleRange, Is.EqualTo(initialVisibleRange * 2).Within(1)));
AddStep("restore zoom", () => TimelineArea.Timeline.Zoom = 100);
AddAssert("range restored", () => Precision.AlmostEquals(TimelineArea.Timeline.VisibleRange, initialVisibleRange, 1));
AddStep("range restored", () => Assert.That(TimelineArea.Timeline.VisibleRange, Is.EqualTo(initialVisibleRange).Within(1)));
}
[Test]

View File

@ -53,7 +53,7 @@ namespace osu.Game.Tests.Visual.Editing
[SetUpSteps]
public void SetUpSteps()
{
AddStep("Stop clock", () => Clock.Stop());
AddStep("Stop clock", () => EditorClock.Stop());
AddUntilStep("wait for rows to load", () => Child.ChildrenOfType<EffectRowAttribute>().Any());
}
@ -68,10 +68,10 @@ namespace osu.Game.Tests.Visual.Editing
});
AddUntilStep("Selection changed", () => timingScreen.SelectedGroup.Value.Time == 54670);
AddUntilStep("Ensure seeked to correct time", () => Clock.CurrentTimeAccurate == 54670);
AddUntilStep("Ensure seeked to correct time", () => EditorClock.CurrentTimeAccurate == 54670);
AddStep("Seek to just before next point", () => Clock.Seek(69000));
AddStep("Start clock", () => Clock.Start());
AddStep("Seek to just before next point", () => EditorClock.Seek(69000));
AddStep("Start clock", () => EditorClock.Start());
AddUntilStep("Selection changed", () => timingScreen.SelectedGroup.Value.Time == 69670);
}
@ -86,9 +86,9 @@ namespace osu.Game.Tests.Visual.Editing
});
AddUntilStep("Selection changed", () => timingScreen.SelectedGroup.Value.Time == 54670);
AddUntilStep("Ensure seeked to correct time", () => Clock.CurrentTimeAccurate == 54670);
AddUntilStep("Ensure seeked to correct time", () => EditorClock.CurrentTimeAccurate == 54670);
AddStep("Seek to later", () => Clock.Seek(80000));
AddStep("Seek to later", () => EditorClock.Seek(80000));
AddUntilStep("Selection changed", () => timingScreen.SelectedGroup.Value.Time == 69670);
}

View File

@ -3,6 +3,7 @@
#nullable disable
using System;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
@ -46,7 +47,7 @@ namespace osu.Game.Tests.Visual.Editing
RelativeSizeAxes = Axes.Both,
Colour = OsuColour.Gray(30)
},
scrollContainer = new ZoomableScrollContainer
scrollContainer = new ZoomableScrollContainer(1, 60, 1)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@ -54,7 +55,7 @@ namespace osu.Game.Tests.Visual.Editing
}
}
},
new MenuCursor()
new MenuCursorContainer()
};
scrollContainer.Add(innerBox = new Box
@ -66,6 +67,18 @@ namespace osu.Game.Tests.Visual.Editing
AddUntilStep("Scroll container is loaded", () => scrollContainer.LoadState >= LoadState.Loaded);
}
[Test]
public void TestInitialZoomOutOfRange()
{
AddStep("Invalid ZoomableScrollContainer throws ArgumentException", () =>
{
Assert.Throws<ArgumentException>(() =>
{
_ = new ZoomableScrollContainer(1, 60, 0);
});
});
}
[Test]
public void TestWidthInitialization()
{
@ -80,21 +93,6 @@ namespace osu.Game.Tests.Visual.Editing
AddAssert("Inner container width matches scroll container", () => innerBox.DrawWidth == scrollContainer.DrawWidth);
}
[Test]
public void TestZoomRangeUpdate()
{
AddStep("set zoom to 2", () => scrollContainer.Zoom = 2);
AddStep("set min zoom to 5", () => scrollContainer.MinZoom = 5);
AddAssert("zoom = 5", () => scrollContainer.Zoom == 5);
AddStep("set max zoom to 10", () => scrollContainer.MaxZoom = 10);
AddAssert("zoom = 5", () => scrollContainer.Zoom == 5);
AddStep("set min zoom to 20", () => scrollContainer.MinZoom = 20);
AddStep("set max zoom to 40", () => scrollContainer.MaxZoom = 40);
AddAssert("zoom = 20", () => scrollContainer.Zoom == 20);
}
[Test]
public void TestZoom0()
{

View File

@ -3,18 +3,21 @@
#nullable disable
using System.Diagnostics;
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.Testing;
using osu.Game.Beatmaps;
using osu.Game.Graphics.Cursor;
using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets.Edit;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Compose.Components.Timeline;
using osu.Game.Storyboards;
using osuTK;
using osuTK.Graphics;
@ -28,10 +31,14 @@ namespace osu.Game.Tests.Visual.Editing
protected EditorBeatmap EditorBeatmap { get; private set; }
[BackgroundDependencyLoader]
private void load(AudioManager audio)
[Resolved]
private AudioManager audio { get; set; }
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) => new WaveformTestBeatmap(audio);
protected override void LoadComplete()
{
Beatmap.Value = new WaveformTestBeatmap(audio);
base.LoadComplete();
var playable = Beatmap.Value.GetPlayableBeatmap(Beatmap.Value.BeatmapInfo.Ruleset);
EditorBeatmap = new EditorBeatmap(playable);
@ -39,7 +46,10 @@ namespace osu.Game.Tests.Visual.Editing
Dependencies.Cache(EditorBeatmap);
Dependencies.CacheAs<IBeatSnapProvider>(EditorBeatmap);
Composer = playable.BeatmapInfo.Ruleset.CreateInstance().CreateHitObjectComposer().With(d => d.Alpha = 0);
Composer = playable.BeatmapInfo.Ruleset.CreateInstance().CreateHitObjectComposer();
Debug.Assert(Composer != null);
Composer.Alpha = 0;
Add(new OsuContextMenuContainer
{
@ -68,11 +78,11 @@ namespace osu.Game.Tests.Visual.Editing
});
}
protected override void LoadComplete()
[SetUpSteps]
public void SetUpSteps()
{
base.LoadComplete();
Clock.Seek(2500);
AddUntilStep("wait for track loaded", () => MusicController.TrackLoaded);
AddStep("seek forward", () => EditorClock.Seek(2500));
}
public abstract Drawable CreateTestComponent();

View File

@ -1,12 +1,11 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
using osu.Game.Skinning;
namespace osu.Game.Tests.Visual.Gameplay
{
@ -19,7 +18,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{
SetContents(skin =>
{
var implementation = skin != null
var implementation = skin is LegacySkin
? CreateLegacyImplementation()
: CreateDefaultImplementation();

View File

@ -31,20 +31,20 @@ namespace osu.Game.Tests.Visual.Gameplay
protected override void AddCheckSteps()
{
// It doesn't matter which ruleset is used - this beatmap is only used for reference.
var beatmap = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
// we only want this beatmap for time reference.
var referenceBeatmap = CreateBeatmap(new OsuRuleset().RulesetInfo);
AddUntilStep("score above zero", () => Player.ScoreProcessor.TotalScore.Value > 0);
AddUntilStep("key counter counted keys", () => Player.HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 2));
seekTo(beatmap.Beatmap.Breaks[0].StartTime);
seekTo(referenceBeatmap.Breaks[0].StartTime);
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));
seekTo(beatmap.Beatmap.HitObjects[^1].GetEndTime());
seekTo(referenceBeatmap.HitObjects[^1].GetEndTime());
AddUntilStep("results displayed", () => getResultsScreen()?.IsLoaded == true);
AddAssert("score has combo", () => getResultsScreen().Score.Combo > 100);

View File

@ -38,7 +38,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestEmptyLegacyBeatmapSkinFallsBack()
{
CreateSkinTest(DefaultSkin.CreateInfo(), () => new LegacyBeatmapSkin(new BeatmapInfo(), null));
CreateSkinTest(TrianglesSkin.CreateInfo(), () => new LegacyBeatmapSkin(new BeatmapInfo(), null));
AddUntilStep("wait for hud load", () => Player.ChildrenOfType<SkinnableTargetContainer>().All(c => c.ComponentsLoaded));
AddAssert("hud from default skin", () => AssertComponentsFromExpectedSource(SkinnableTarget.MainHUDComponents, skinManager.CurrentSkin.Value));
}
@ -80,14 +80,14 @@ namespace osu.Game.Tests.Visual.Gameplay
(typeof(ScoreProcessor), actualComponentsContainer.Dependencies.Get<ScoreProcessor>()),
(typeof(HealthProcessor), actualComponentsContainer.Dependencies.Get<HealthProcessor>()),
(typeof(GameplayState), actualComponentsContainer.Dependencies.Get<GameplayState>()),
(typeof(GameplayClock), actualComponentsContainer.Dependencies.Get<GameplayClock>())
(typeof(IGameplayClock), actualComponentsContainer.Dependencies.Get<IGameplayClock>())
},
};
Add(expectedComponentsAdjustmentContainer);
expectedComponentsAdjustmentContainer.UpdateSubTree();
var expectedInfo = expectedComponentsContainer.CreateSkinnableInfo();
Remove(expectedComponentsAdjustmentContainer);
Remove(expectedComponentsAdjustmentContainer, true);
return almostEqual(actualInfo, expectedInfo);
}
@ -122,7 +122,7 @@ namespace osu.Game.Tests.Visual.Gameplay
private class TestOsuRuleset : OsuRuleset
{
public override ISkin CreateLegacySkinProvider(ISkin skin, IBeatmap beatmap) => new TestOsuLegacySkinTransformer(skin);
public override ISkin CreateSkinTransformer(ISkin skin, IBeatmap beatmap) => new TestOsuLegacySkinTransformer(skin);
private class TestOsuLegacySkinTransformer : OsuLegacySkinTransformer
{

View File

@ -0,0 +1,130 @@
// 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.Audio;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Framework.Timing;
using osu.Game.Screens.Play;
using osu.Game.Screens.Play.HUD.ClicksPerSecond;
using osuTK;
namespace osu.Game.Tests.Visual.Gameplay
{
public class TestSceneClicksPerSecondCalculator : OsuTestScene
{
private ClicksPerSecondCalculator calculator = null!;
private TestGameplayClock manualGameplayClock = null!;
[SetUpSteps]
public void SetUpSteps()
{
AddStep("create components", () =>
{
manualGameplayClock = new TestGameplayClock();
Child = new DependencyProvidingContainer
{
RelativeSizeAxes = Axes.Both,
CachedDependencies = new (Type, object)[] { (typeof(IGameplayClock), manualGameplayClock) },
Children = new Drawable[]
{
calculator = new ClicksPerSecondCalculator(),
new DependencyProvidingContainer
{
RelativeSizeAxes = Axes.Both,
CachedDependencies = new (Type, object)[] { (typeof(ClicksPerSecondCalculator), calculator) },
Child = new ClicksPerSecondCounter
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Scale = new Vector2(5),
}
}
},
};
});
}
[Test]
public void TestBasicConsistency()
{
seek(1000);
AddStep("add inputs in past", () => addInputs(new double[] { 0, 100, 200, 300, 400, 500, 600, 700, 800, 900 }));
checkClicksPerSecondValue(10);
}
[Test]
public void TestRateAdjustConsistency()
{
seek(1000);
AddStep("add inputs in past", () => addInputs(new double[] { 0, 100, 200, 300, 400, 500, 600, 700, 800, 900 }));
checkClicksPerSecondValue(10);
AddStep("set rate 0.5x", () => manualGameplayClock.TrueGameplayRate = 0.5);
checkClicksPerSecondValue(5);
}
[Test]
public void TestInputsDiscardedOnRewind()
{
seek(1000);
AddStep("add inputs in past", () => addInputs(new double[] { 0, 100, 200, 300, 400, 500, 600, 700, 800, 900 }));
checkClicksPerSecondValue(10);
seek(500);
checkClicksPerSecondValue(6);
seek(1000);
checkClicksPerSecondValue(6);
}
private void checkClicksPerSecondValue(int i) => AddAssert("clicks/s is correct", () => calculator.Value, () => Is.EqualTo(i));
private void seekClockImmediately(double time) => manualGameplayClock.CurrentTime = time;
private void seek(double time) => AddStep($"Seek to {time}ms", () => seekClockImmediately(time));
private void addInputs(IEnumerable<double> inputs)
{
double baseTime = manualGameplayClock.CurrentTime;
foreach (double timestamp in inputs)
{
seekClockImmediately(timestamp);
calculator.AddInputTimestamp();
}
seekClockImmediately(baseTime);
}
private class TestGameplayClock : IGameplayClock
{
public double CurrentTime { get; set; }
public double Rate => 1;
public bool IsRunning => true;
public double TrueGameplayRate { set => adjustableAudioComponent.Tempo.Value = value; }
private readonly AudioAdjustments adjustableAudioComponent = new AudioAdjustments();
public void ProcessFrame()
{
}
public double ElapsedFrameTime => throw new NotImplementedException();
public double FramesPerSecond => throw new NotImplementedException();
public FrameTimeInfo TimeInfo => throw new NotImplementedException();
public double StartTime => throw new NotImplementedException();
public IAdjustableAudioComponent AdjustmentsFromMods => adjustableAudioComponent;
public IEnumerable<double> NonGameplayAdjustments => throw new NotImplementedException();
public IBindable<bool> IsPaused => throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,117 @@
// 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.Diagnostics;
using NUnit.Framework;
using osu.Framework.Bindables;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Judgements;
using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Play.HUD.HitErrorMeters;
using osuTK;
namespace osu.Game.Tests.Visual.Gameplay
{
public class TestSceneColourHitErrorMeter : OsuTestScene
{
private DependencyProvidingContainer dependencyContainer = null!;
private readonly Bindable<JudgementResult> lastJudgementResult = new Bindable<JudgementResult>();
private ScoreProcessor scoreProcessor = null!;
private int iteration;
private ColourHitErrorMeter colourHitErrorMeter = null!;
public TestSceneColourHitErrorMeter()
{
AddSliderStep("Judgement spacing", 0, 10, 2, spacing =>
{
if (colourHitErrorMeter.IsNotNull())
colourHitErrorMeter.JudgementSpacing.Value = spacing;
});
AddSliderStep("Judgement count", 1, 50, 5, spacing =>
{
if (colourHitErrorMeter.IsNotNull())
colourHitErrorMeter.JudgementCount.Value = spacing;
});
}
[SetUpSteps]
public void SetupSteps() => AddStep("Create components", () =>
{
var ruleset = CreateRuleset();
Debug.Assert(ruleset != null);
scoreProcessor = new ScoreProcessor(ruleset);
Child = dependencyContainer = new DependencyProvidingContainer
{
RelativeSizeAxes = Axes.Both,
CachedDependencies = new (Type, object)[]
{
(typeof(ScoreProcessor), scoreProcessor)
}
};
dependencyContainer.Child = colourHitErrorMeter = new ColourHitErrorMeter
{
Margin = new MarginPadding
{
Top = 100
},
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Scale = new Vector2(2),
};
});
protected override Ruleset CreateRuleset() => new OsuRuleset();
[Test]
public void TestSpacingChange()
{
AddRepeatStep("Add judgement", applyOneJudgement, 5);
AddStep("Change spacing", () => colourHitErrorMeter.JudgementSpacing.Value = 10);
AddRepeatStep("Add judgement", applyOneJudgement, 5);
}
[Test]
public void TestJudgementAmountChange()
{
AddRepeatStep("Add judgement", applyOneJudgement, 10);
AddStep("Judgement count change to 4", () => colourHitErrorMeter.JudgementCount.Value = 4);
AddRepeatStep("Add judgement", applyOneJudgement, 8);
}
[Test]
public void TestHitErrorShapeChange()
{
AddRepeatStep("Add judgement", applyOneJudgement, 8);
AddStep("Change shape square", () => colourHitErrorMeter.JudgementShape.Value = ColourHitErrorMeter.ShapeStyle.Square);
AddRepeatStep("Add judgement", applyOneJudgement, 10);
AddStep("Change shape circle", () => colourHitErrorMeter.JudgementShape.Value = ColourHitErrorMeter.ShapeStyle.Circle);
}
private void applyOneJudgement()
{
lastJudgementResult.Value = new OsuJudgementResult(new HitObject
{
StartTime = iteration * 10000,
}, new OsuJudgement())
{
Type = HitResult.Great,
};
scoreProcessor.ApplyResult(lastJudgementResult.Value);
iteration++;
}
}
}

View File

@ -263,27 +263,30 @@ namespace osu.Game.Tests.Visual.Gameplay
return beatmap;
}
private void createTest(IBeatmap beatmap, Action<TestDrawableScrollingRuleset> overrideAction = null) => AddStep("create test", () =>
private void createTest(IBeatmap beatmap, Action<TestDrawableScrollingRuleset> overrideAction = null)
{
var ruleset = new TestScrollingRuleset();
drawableRuleset = (TestDrawableScrollingRuleset)ruleset.CreateDrawableRulesetWith(CreateWorkingBeatmap(beatmap).GetPlayableBeatmap(ruleset.RulesetInfo));
drawableRuleset.FrameStablePlayback = false;
overrideAction?.Invoke(drawableRuleset);
Child = new Container
AddStep("create test", () =>
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Y,
Height = 0.75f,
Width = 400,
Masking = true,
Clock = new FramedClock(testClock),
Child = drawableRuleset
};
});
var ruleset = new TestScrollingRuleset();
drawableRuleset = (TestDrawableScrollingRuleset)ruleset.CreateDrawableRulesetWith(CreateWorkingBeatmap(beatmap).GetPlayableBeatmap(ruleset.RulesetInfo));
drawableRuleset.FrameStablePlayback = false;
overrideAction?.Invoke(drawableRuleset);
Child = new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Y,
Height = 0.75f,
Width = 400,
Masking = true,
Clock = new FramedClock(testClock),
Child = drawableRuleset
};
});
}
#region Ruleset

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.
#nullable disable
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
@ -73,6 +71,18 @@ namespace osu.Game.Tests.Visual.Gameplay
AddAssert("origin flipped", () => sprites.All(s => s.Origin == Anchor.BottomRight));
}
[Test]
public void TestZeroScale()
{
const string lookup_name = "hitcircleoverlay";
AddStep("allow skin lookup", () => storyboard.UseSkinSprites = true);
AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero)));
AddAssert("sprites present", () => sprites.All(s => s.IsPresent));
AddStep("scale sprite", () => sprites.ForEach(s => s.VectorScale = new Vector2(0, 1)));
AddAssert("sprites not present", () => sprites.All(s => !s.IsPresent));
}
[Test]
public void TestNegativeScale()
{

View File

@ -137,13 +137,13 @@ namespace osu.Game.Tests.Visual.Gameplay
private void seekManualTo(double time) => AddStep($"seek manual clock to {time}", () => manualClock.CurrentTime = time);
private void confirmSeek(double time) => AddUntilStep($"wait for seek to {time}", () => consumer.Clock.CurrentTime == time);
private void confirmSeek(double time) => AddUntilStep($"wait for seek to {time}", () => consumer.Clock.CurrentTime, () => Is.EqualTo(time));
private void checkFrameCount(int frames) =>
AddAssert($"elapsed frames is {frames}", () => consumer.ElapsedFrames == frames);
AddAssert($"elapsed frames is {frames}", () => consumer.ElapsedFrames, () => Is.EqualTo(frames));
private void checkRate(double rate) =>
AddAssert($"clock rate is {rate}", () => consumer.Clock.Rate == rate);
AddAssert($"clock rate is {rate}", () => consumer.Clock.Rate, () => Is.EqualTo(rate));
public class ClockConsumingChild : CompositeDrawable
{

View File

@ -6,7 +6,9 @@
using System.Linq;
using NUnit.Framework;
using osu.Framework.Bindables;
using osu.Framework.Extensions.PolygonExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Online.API.Requests.Responses;
@ -18,37 +20,62 @@ namespace osu.Game.Tests.Visual.Gameplay
[TestFixture]
public class TestSceneGameplayLeaderboard : OsuTestScene
{
private readonly TestGameplayLeaderboard leaderboard;
private TestGameplayLeaderboard leaderboard;
private readonly BindableDouble playerScore = new BindableDouble();
public TestSceneGameplayLeaderboard()
{
Add(leaderboard = new TestGameplayLeaderboard
AddStep("toggle expanded", () =>
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Scale = new Vector2(2),
if (leaderboard != null)
leaderboard.Expanded.Value = !leaderboard.Expanded.Value;
});
AddSliderStep("set player score", 50, 5000000, 1222333, v => playerScore.Value = v);
}
[SetUpSteps]
public void SetUpSteps()
[Test]
public void TestLayoutWithManyScores()
{
AddStep("reset leaderboard", () =>
createLeaderboard();
AddStep("add many scores in one go", () =>
{
leaderboard.Clear();
playerScore.Value = 1222333;
for (int i = 0; i < 32; i++)
createRandomScore(new APIUser { Username = $"Player {i + 1}" });
// Add player at end to force an animation down the whole list.
playerScore.Value = 0;
createLeaderboardScore(playerScore, new APIUser { Username = "You", Id = 3 }, true);
});
AddStep("add local player", () => createLeaderboardScore(playerScore, new APIUser { Username = "You", Id = 3 }, true));
AddStep("toggle expanded", () => leaderboard.Expanded.Value = !leaderboard.Expanded.Value);
AddSliderStep("set player score", 50, 5000000, 1222333, v => playerScore.Value = v);
// Gameplay leaderboard has custom scroll logic, which when coupled with LayoutDuration
// has caused layout to not work in the past.
AddUntilStep("wait for fill flow layout",
() => leaderboard.ChildrenOfType<FillFlowContainer<GameplayLeaderboardScore>>().First().ScreenSpaceDrawQuad.Intersects(leaderboard.ScreenSpaceDrawQuad));
AddUntilStep("wait for some scores not masked away",
() => leaderboard.ChildrenOfType<GameplayLeaderboardScore>().Any(s => leaderboard.ScreenSpaceDrawQuad.Contains(s.ScreenSpaceDrawQuad.Centre)));
AddUntilStep("wait for tracked score fully visible", () => leaderboard.ScreenSpaceDrawQuad.Intersects(leaderboard.TrackedScore!.ScreenSpaceDrawQuad));
AddStep("change score to middle", () => playerScore.Value = 1000000);
AddWaitStep("wait for movement", 5);
AddUntilStep("wait for tracked score fully visible", () => leaderboard.ScreenSpaceDrawQuad.Intersects(leaderboard.TrackedScore!.ScreenSpaceDrawQuad));
AddStep("change score to first", () => playerScore.Value = 5000000);
AddWaitStep("wait for movement", 5);
AddUntilStep("wait for tracked score fully visible", () => leaderboard.ScreenSpaceDrawQuad.Intersects(leaderboard.TrackedScore!.ScreenSpaceDrawQuad));
}
[Test]
public void TestPlayerScore()
{
createLeaderboard();
addLocalPlayer();
var player2Score = new BindableDouble(1234567);
var player3Score = new BindableDouble(1111111);
@ -73,6 +100,9 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestRandomScores()
{
createLeaderboard();
addLocalPlayer();
int playerNumber = 1;
AddRepeatStep("add player with random score", () => createRandomScore(new APIUser { Username = $"Player {playerNumber++}" }), 10);
}
@ -80,6 +110,9 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestExistingUsers()
{
createLeaderboard();
addLocalPlayer();
AddStep("add peppy", () => createRandomScore(new APIUser { Username = "peppy", Id = 2 }));
AddStep("add smoogipoo", () => createRandomScore(new APIUser { Username = "smoogipoo", Id = 1040328 }));
AddStep("add flyte", () => createRandomScore(new APIUser { Username = "flyte", Id = 3103765 }));
@ -89,6 +122,9 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestMaxHeight()
{
createLeaderboard();
addLocalPlayer();
int playerNumber = 1;
AddRepeatStep("add 3 other players", () => createRandomScore(new APIUser { Username = $"Player {playerNumber++}" }), 3);
checkHeight(4);
@ -103,6 +139,28 @@ namespace osu.Game.Tests.Visual.Gameplay
=> AddAssert($"leaderboard height is {panelCount} panels high", () => leaderboard.DrawHeight == (GameplayLeaderboardScore.PANEL_HEIGHT + leaderboard.Spacing) * panelCount);
}
private void addLocalPlayer()
{
AddStep("add local player", () =>
{
playerScore.Value = 1222333;
createLeaderboardScore(playerScore, new APIUser { Username = "You", Id = 3 }, true);
});
}
private void createLeaderboard()
{
AddStep("create leaderboard", () =>
{
Child = leaderboard = new TestGameplayLeaderboard
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Scale = new Vector2(2),
};
});
}
private void createRandomScore(APIUser user) => createLeaderboardScore(new BindableDouble(RNG.Next(0, 5_000_000)), user);
private void createLeaderboardScore(BindableDouble score, APIUser user, bool isTracked = false)

View File

@ -31,7 +31,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning);
addSeekStep(3000);
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));
AddUntilStep("key counter counted keys", () => Player.HUDOverlay.KeyCounter.Children.Select(kc => kc.CountPresses).Sum() == 15);
AddStep("clear results", () => Player.Results.Clear());
addSeekStep(0);
AddAssert("none judged", () => Player.DrawableRuleset.Playfield.AllHitObjects.All(h => !h.Judged));

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.
#nullable disable
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
@ -21,22 +19,22 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestAllSamplesStopDuringSeek()
{
DrawableSlider slider = null;
PoolableSkinnableSample[] samples = null;
ISamplePlaybackDisabler sampleDisabler = null;
DrawableSlider? slider = null;
PoolableSkinnableSample[] samples = null!;
ISamplePlaybackDisabler sampleDisabler = null!;
AddUntilStep("get variables", () =>
{
sampleDisabler = Player;
slider = Player.ChildrenOfType<DrawableSlider>().MinBy(s => s.HitObject.StartTime);
samples = slider?.ChildrenOfType<PoolableSkinnableSample>().ToArray();
samples = slider.ChildrenOfType<PoolableSkinnableSample>().ToArray();
return slider != null;
});
AddUntilStep("wait for slider sliding then seek", () =>
{
if (!slider.Tracking.Value)
if (slider?.Tracking.Value != true)
return false;
if (!samples.Any(s => s.Playing))

View File

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

View File

@ -16,6 +16,7 @@ using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Play;
using osu.Game.Screens.Play.HUD;
using osu.Game.Screens.Play.HUD.HitErrorMeters;
using osu.Game.Skinning;
using osu.Game.Tests.Gameplay;
@ -38,8 +39,8 @@ namespace osu.Game.Tests.Visual.Gameplay
[Cached]
private GameplayState gameplayState = TestGameplayState.Create(new OsuRuleset());
[Cached]
private readonly GameplayClock gameplayClock = new GameplayClock(new FramedClock());
[Cached(typeof(IGameplayClock))]
private readonly IGameplayClock gameplayClock = new GameplayClockContainer(new FramedClock());
// best way to check without exposing.
private Drawable hideTarget => hudOverlay.KeyCounter;
@ -148,6 +149,42 @@ namespace osu.Game.Tests.Visual.Gameplay
AddAssert("key counters still hidden", () => !keyCounterFlow.IsPresent);
}
[Test]
public void TestInputDoesntWorkWhenHUDHidden()
{
SongProgressBar getSongProgress() => hudOverlay.ChildrenOfType<SongProgressBar>().Single();
bool seeked = false;
createNew();
AddStep("bind seek", () =>
{
seeked = false;
var progress = getSongProgress();
progress.ShowHandle = true;
progress.OnSeek += _ => seeked = true;
});
AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false);
AddUntilStep("hidetarget is hidden", () => !hideTarget.IsPresent);
AddStep("attempt seek", () =>
{
InputManager.MoveMouseTo(getSongProgress());
InputManager.Click(MouseButton.Left);
});
AddAssert("seek not performed", () => !seeked);
AddStep("set showhud true", () => hudOverlay.ShowHud.Value = true);
AddStep("attempt seek", () => InputManager.Click(MouseButton.Left));
AddAssert("seek performed", () => seeked);
}
[Test]
public void TestHiddenHUDDoesntBlockComponentUpdates()
{
@ -159,6 +196,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("wait for hud load", () => hudOverlay.IsLoaded);
AddUntilStep("wait for components to be hidden", () => hudOverlay.ChildrenOfType<SkinnableTargetContainer>().Single().Alpha == 0);
AddUntilStep("wait for hud load", () => hudOverlay.ChildrenOfType<SkinnableTargetContainer>().All(c => c.ComponentsLoaded));
AddStep("bind on update", () =>
{

View File

@ -107,13 +107,13 @@ namespace osu.Game.Tests.Visual.Gameplay
AddAssert("no bars added", () => !this.ChildrenOfType<BarHitErrorMeter.JudgementLine>().Any());
AddAssert("circle added", () =>
this.ChildrenOfType<ColourHitErrorMeter>().All(
meter => meter.ChildrenOfType<ColourHitErrorMeter.HitErrorCircle>().Count() == 1));
meter => meter.ChildrenOfType<ColourHitErrorMeter.HitErrorShape>().Count() == 1));
AddStep("miss", () => newJudgement(50, HitResult.Miss));
AddAssert("no bars added", () => !this.ChildrenOfType<BarHitErrorMeter.JudgementLine>().Any());
AddAssert("circle added", () =>
this.ChildrenOfType<ColourHitErrorMeter>().All(
meter => meter.ChildrenOfType<ColourHitErrorMeter.HitErrorCircle>().Count() == 2));
meter => meter.ChildrenOfType<ColourHitErrorMeter.HitErrorShape>().Count() == 2));
}
[Test]
@ -123,11 +123,11 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("small bonus", () => newJudgement(result: HitResult.SmallBonus));
AddAssert("no bars added", () => !this.ChildrenOfType<BarHitErrorMeter.JudgementLine>().Any());
AddAssert("no circle added", () => !this.ChildrenOfType<ColourHitErrorMeter.HitErrorCircle>().Any());
AddAssert("no circle added", () => !this.ChildrenOfType<ColourHitErrorMeter.HitErrorShape>().Any());
AddStep("large bonus", () => newJudgement(result: HitResult.LargeBonus));
AddAssert("no bars added", () => !this.ChildrenOfType<BarHitErrorMeter.JudgementLine>().Any());
AddAssert("no circle added", () => !this.ChildrenOfType<ColourHitErrorMeter.HitErrorCircle>().Any());
AddAssert("no circle added", () => !this.ChildrenOfType<ColourHitErrorMeter.HitErrorShape>().Any());
}
[Test]
@ -137,16 +137,17 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("ignore hit", () => newJudgement(result: HitResult.IgnoreHit));
AddAssert("no bars added", () => !this.ChildrenOfType<BarHitErrorMeter.JudgementLine>().Any());
AddAssert("no circle added", () => !this.ChildrenOfType<ColourHitErrorMeter.HitErrorCircle>().Any());
AddAssert("no circle added", () => !this.ChildrenOfType<ColourHitErrorMeter.HitErrorShape>().Any());
AddStep("ignore miss", () => newJudgement(result: HitResult.IgnoreMiss));
AddAssert("no bars added", () => !this.ChildrenOfType<BarHitErrorMeter.JudgementLine>().Any());
AddAssert("no circle added", () => !this.ChildrenOfType<ColourHitErrorMeter.HitErrorCircle>().Any());
AddAssert("no circle added", () => !this.ChildrenOfType<ColourHitErrorMeter.HitErrorShape>().Any());
}
[Test]
public void TestProcessingWhileHidden()
{
const int max_displayed_judgements = 20;
AddStep("OD 1", () => recreateDisplay(new OsuHitWindows(), 1));
AddStep("hide displays", () =>
@ -155,16 +156,16 @@ namespace osu.Game.Tests.Visual.Gameplay
hitErrorMeter.Hide();
});
AddRepeatStep("hit", () => newJudgement(), ColourHitErrorMeter.MAX_DISPLAYED_JUDGEMENTS * 2);
AddRepeatStep("hit", () => newJudgement(), max_displayed_judgements * 2);
AddAssert("bars added", () => this.ChildrenOfType<BarHitErrorMeter.JudgementLine>().Any());
AddAssert("circle added", () => this.ChildrenOfType<ColourHitErrorMeter.HitErrorCircle>().Any());
AddAssert("circle added", () => this.ChildrenOfType<ColourHitErrorMeter.HitErrorShape>().Any());
AddUntilStep("wait for bars to disappear", () => !this.ChildrenOfType<BarHitErrorMeter.JudgementLine>().Any());
AddUntilStep("ensure max circles not exceeded", () =>
{
return this.ChildrenOfType<ColourHitErrorMeter>()
.All(m => m.ChildrenOfType<ColourHitErrorMeter.HitErrorCircle>().Count() <= ColourHitErrorMeter.MAX_DISPLAYED_JUDGEMENTS);
.All(m => m.ChildrenOfType<ColourHitErrorMeter.HitErrorShape>().Count() <= max_displayed_judgements);
});
AddStep("show displays", () =>
@ -183,12 +184,12 @@ namespace osu.Game.Tests.Visual.Gameplay
AddAssert("bar added", () => this.ChildrenOfType<BarHitErrorMeter>().All(
meter => meter.ChildrenOfType<BarHitErrorMeter.JudgementLine>().Count() == 1));
AddAssert("circle added", () => this.ChildrenOfType<ColourHitErrorMeter>().All(
meter => meter.ChildrenOfType<ColourHitErrorMeter.HitErrorCircle>().Count() == 1));
meter => meter.ChildrenOfType<ColourHitErrorMeter.HitErrorShape>().Count() == 1));
AddStep("clear", () => this.ChildrenOfType<HitErrorMeter>().ForEach(meter => meter.Clear()));
AddAssert("bar cleared", () => !this.ChildrenOfType<BarHitErrorMeter.JudgementLine>().Any());
AddAssert("colour cleared", () => !this.ChildrenOfType<ColourHitErrorMeter.HitErrorCircle>().Any());
AddAssert("colour cleared", () => !this.ChildrenOfType<ColourHitErrorMeter.HitErrorShape>().Any());
}
private void recreateDisplay(HitWindows hitWindows, float overallDifficulty)

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.
#nullable disable
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
@ -19,9 +17,9 @@ namespace osu.Game.Tests.Visual.Gameplay
{
public class TestSceneLeadIn : RateAdjustedBeatmapTestScene
{
private LeadInPlayer player;
private LeadInPlayer player = null!;
private const double lenience_ms = 10;
private const double lenience_ms = 100;
private const double first_hit_object = 2170;
@ -36,11 +34,7 @@ namespace osu.Game.Tests.Visual.Gameplay
BeatmapInfo = { AudioLeadIn = leadIn }
});
AddStep("check first frame time", () =>
{
Assert.That(player.FirstFrameClockTime, Is.Not.Null);
Assert.That(player.FirstFrameClockTime.Value, Is.EqualTo(expectedStartTime).Within(lenience_ms));
});
checkFirstFrameTime(expectedStartTime);
}
[TestCase(1000, 0)]
@ -59,11 +53,7 @@ namespace osu.Game.Tests.Visual.Gameplay
loadPlayerWithBeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo), storyboard);
AddStep("check first frame time", () =>
{
Assert.That(player.FirstFrameClockTime, Is.Not.Null);
Assert.That(player.FirstFrameClockTime.Value, Is.EqualTo(expectedStartTime).Within(lenience_ms));
});
checkFirstFrameTime(expectedStartTime);
}
[TestCase(1000, 0, false)]
@ -76,18 +66,20 @@ namespace osu.Game.Tests.Visual.Gameplay
[TestCase(-10000, -10000, true)]
public void TestStoryboardProducesCorrectStartTimeFadeInAfterOtherEvents(double firstStoryboardEvent, double expectedStartTime, bool addEventToLoop)
{
const double loop_start_time = -20000;
var storyboard = new Storyboard();
var sprite = new StoryboardSprite("unknown", Anchor.TopLeft, Vector2.Zero);
// these should be ignored as we have an alpha visibility blocker proceeding this command.
sprite.TimelineGroup.Scale.Add(Easing.None, -20000, -18000, 0, 1);
var loopGroup = sprite.AddLoop(-20000, 50);
loopGroup.Scale.Add(Easing.None, -20000, -18000, 0, 1);
sprite.TimelineGroup.Scale.Add(Easing.None, loop_start_time, -18000, 0, 1);
var loopGroup = sprite.AddLoop(loop_start_time, 50);
loopGroup.Scale.Add(Easing.None, loop_start_time, -18000, 0, 1);
var target = addEventToLoop ? loopGroup : sprite.TimelineGroup;
double targetTime = addEventToLoop ? 20000 : 0;
target.Alpha.Add(Easing.None, targetTime + firstStoryboardEvent, targetTime + firstStoryboardEvent + 500, 0, 1);
double loopRelativeOffset = addEventToLoop ? -loop_start_time : 0;
target.Alpha.Add(Easing.None, loopRelativeOffset + firstStoryboardEvent, loopRelativeOffset + firstStoryboardEvent + 500, 0, 1);
// these should be ignored due to being in the future.
sprite.TimelineGroup.Alpha.Add(Easing.None, 18000, 20000, 0, 1);
@ -97,14 +89,13 @@ namespace osu.Game.Tests.Visual.Gameplay
loadPlayerWithBeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo), storyboard);
AddStep("check first frame time", () =>
{
Assert.That(player.FirstFrameClockTime, Is.Not.Null);
Assert.That(player.FirstFrameClockTime.Value, Is.EqualTo(expectedStartTime).Within(lenience_ms));
});
checkFirstFrameTime(expectedStartTime);
}
private void loadPlayerWithBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
private void checkFirstFrameTime(double expectedStartTime) =>
AddAssert("check first frame time", () => player.FirstFrameClockTime, () => Is.EqualTo(expectedStartTime).Within(lenience_ms));
private void loadPlayerWithBeatmap(IBeatmap beatmap, Storyboard? storyboard = null)
{
AddStep("create player", () =>
{
@ -126,19 +117,15 @@ namespace osu.Game.Tests.Visual.Gameplay
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;
FirstFrameClockTime = GameplayClockContainer.CurrentTime;
AddInternal(new OsuSpriteText
{
Text = $"GameplayStartTime: {DrawableRuleset.GameplayStartTime} "

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.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Testing;
using osu.Game.Rulesets.Mods;
namespace osu.Game.Tests.Visual.Gameplay
{
[HeadlessTest]
public class TestSceneModValidity : TestSceneAllRulesetPlayers
{
protected override void AddCheckSteps()
{
AddStep("Check all mod acronyms are unique", () =>
{
var mods = Ruleset.Value.CreateInstance().AllMods;
IEnumerable<string> acronyms = mods.Select(m => m.Acronym);
Assert.That(acronyms, Is.Unique);
});
AddStep("Check all mods are two-way incompatible", () =>
{
var mods = Ruleset.Value.CreateInstance().AllMods;
IEnumerable<Mod> modInstances = mods.Select(mod => mod.CreateInstance());
foreach (var modToCheck in modInstances)
{
var incompatibleMods = modToCheck.IncompatibleMods;
foreach (var incompatible in incompatibleMods)
{
foreach (var incompatibleMod in modInstances.Where(m => incompatible.IsInstanceOfType(m)))
{
Assert.That(
incompatibleMod.IncompatibleMods.Any(m => m.IsInstanceOfType(modToCheck)),
$"{modToCheck} has {incompatibleMod} in it's incompatible mods, but {incompatibleMod} does not have {modToCheck} in it's incompatible mods."
);
}
}
}
});
}
}
}

View File

@ -19,7 +19,7 @@ namespace osu.Game.Tests.Visual.Gameplay
base.SetUpSteps();
AddUntilStep("gameplay has started",
() => Player.GameplayClockContainer.GameplayClock.CurrentTime > Player.DrawableRuleset.GameplayStartTime);
() => Player.GameplayClockContainer.CurrentTime > Player.DrawableRuleset.GameplayStartTime);
}
[Test]

View File

@ -79,7 +79,7 @@ namespace osu.Game.Tests.Visual.Gameplay
}
private TestParticleSpewer createSpewer() =>
new TestParticleSpewer(skinManager.DefaultLegacySkin.GetTexture("star2"))
new TestParticleSpewer(skinManager.DefaultClassicSkin.GetTexture("star2"))
{
Origin = Anchor.Centre,
RelativePositionAxes = Axes.Both,

View File

@ -31,7 +31,7 @@ namespace osu.Game.Tests.Visual.Gameplay
public TestScenePause()
{
base.Content.Add(content = new MenuCursorContainer { RelativeSizeAxes = Axes.Both });
base.Content.Add(content = new GlobalCursorDisplay { RelativeSizeAxes = Axes.Both });
}
[SetUpSteps]
@ -66,7 +66,7 @@ namespace osu.Game.Tests.Visual.Gameplay
Player.OnUpdate += _ =>
{
double currentTime = Player.GameplayClockContainer.CurrentTime;
alwaysGoingForward &= currentTime >= lastTime;
alwaysGoingForward &= currentTime >= lastTime - 500;
lastTime = currentTime;
};
});
@ -77,7 +77,7 @@ namespace osu.Game.Tests.Visual.Gameplay
resumeAndConfirm();
AddAssert("time didn't go backwards", () => alwaysGoingForward);
AddAssert("time didn't go too far backwards", () => alwaysGoingForward);
AddStep("reset offset", () => LocalConfig.SetValue(OsuSetting.AudioOffset, 0.0));
}
@ -90,6 +90,9 @@ namespace osu.Game.Tests.Visual.Gameplay
AddAssert("player not playing", () => !Player.LocalUserPlaying.Value);
resumeAndConfirm();
AddAssert("Resumed without seeking forward", () => Player.LastResumeTime, () => Is.LessThanOrEqualTo(Player.LastPauseTime));
AddUntilStep("player playing", () => Player.LocalUserPlaying.Value);
}
@ -313,7 +316,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("pause again", () =>
{
Player.Pause();
return !Player.GameplayClockContainer.GameplayClock.IsRunning;
return !Player.GameplayClockContainer.IsRunning;
});
AddAssert("loop is playing", () => getLoop().IsPlaying);
@ -367,7 +370,7 @@ namespace osu.Game.Tests.Visual.Gameplay
private void confirmNoTrackAdjustments()
{
AddAssert("track has no adjustments", () => Beatmap.Value.Track.AggregateFrequency.Value == 1);
AddUntilStep("track has no adjustments", () => Beatmap.Value.Track.AggregateFrequency.Value, () => Is.EqualTo(1));
}
private void restart() => AddStep("restart", () => Player.Restart());
@ -378,7 +381,16 @@ namespace osu.Game.Tests.Visual.Gameplay
AddAssert("pause overlay " + (isShown ? "shown" : "hidden"), () => Player.PauseOverlayVisible == isShown);
private void confirmClockRunning(bool isRunning) =>
AddUntilStep("clock " + (isRunning ? "running" : "stopped"), () => Player.GameplayClockContainer.GameplayClock.IsRunning == isRunning);
AddUntilStep("clock " + (isRunning ? "running" : "stopped"), () =>
{
bool completed = Player.GameplayClockContainer.IsRunning == isRunning;
if (completed)
{
}
return completed;
});
protected override bool AllowFail => true;
@ -386,6 +398,9 @@ namespace osu.Game.Tests.Visual.Gameplay
protected class PausePlayer : TestPlayer
{
public double LastPauseTime { get; private set; }
public double LastResumeTime { get; private set; }
public bool FailOverlayVisible => FailOverlay.State.Value == Visibility.Visible;
public bool PauseOverlayVisible => PauseOverlay.State.Value == Visibility.Visible;
@ -399,6 +414,23 @@ namespace osu.Game.Tests.Visual.Gameplay
base.OnEntering(e);
GameplayClockContainer.Stop();
}
private bool? isRunning;
protected override void UpdateAfterChildren()
{
base.UpdateAfterChildren();
if (GameplayClockContainer.IsRunning != isRunning)
{
isRunning = GameplayClockContainer.IsRunning;
if (isRunning.Value)
LastResumeTime = GameplayClockContainer.CurrentTime;
else
LastPauseTime = GameplayClockContainer.CurrentTime;
}
}
}
}
}

View File

@ -13,11 +13,11 @@ using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Utils;
using osu.Framework.Localisation;
using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Configuration;
using osu.Game.Graphics.Containers;
using osu.Game.Overlays;
using osu.Game.Overlays.Notifications;
using osu.Game.Rulesets.Mods;
@ -56,6 +56,10 @@ namespace osu.Game.Tests.Visual.Gameplay
private readonly ChangelogOverlay changelogOverlay;
private double savedTrackVolume;
private double savedMasterVolume;
private bool savedMutedState;
public TestScenePlayerLoader()
{
AddRange(new Drawable[]
@ -75,11 +79,21 @@ namespace osu.Game.Tests.Visual.Gameplay
}
[SetUp]
public void Setup() => Schedule(() =>
public void Setup() => Schedule(() => player = null);
[SetUpSteps]
public override void SetUpSteps()
{
player = null;
audioManager.Volume.SetDefault();
});
base.SetUpSteps();
AddStep("read all notifications", () =>
{
notificationOverlay.Show();
notificationOverlay.Hide();
});
AddUntilStep("wait for no notifications", () => notificationOverlay.UnreadCount.Value, () => Is.EqualTo(0));
}
/// <summary>
/// Sets the input manager child to a new test player loader container instance.
@ -98,7 +112,13 @@ namespace osu.Game.Tests.Visual.Gameplay
private void prepareBeatmap()
{
var workingBeatmap = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
// Add intro time to test quick retry skipping (TestQuickRetry).
workingBeatmap.BeatmapInfo.AudioLeadIn = 60000;
// Turn on epilepsy warning to test warning display (TestEpilepsyWarning).
workingBeatmap.BeatmapInfo.EpilepsyWarning = epilepsyWarning;
Beatmap.Value = workingBeatmap;
foreach (var mod in SelectedMods.Value.OfType<IApplicableToTrack>())
@ -147,6 +167,7 @@ namespace osu.Game.Tests.Visual.Gameplay
moveMouse();
return player?.LoadState == LoadState.Ready;
});
AddRepeatStep("move mouse", moveMouse, 20);
AddAssert("loader still active", () => loader.IsCurrentScreen());
@ -154,6 +175,8 @@ namespace osu.Game.Tests.Visual.Gameplay
void moveMouse()
{
notificationOverlay.State.Value = Visibility.Hidden;
InputManager.MoveMouseTo(
loader.VisualSettings.ScreenSpaceDrawQuad.TopLeft
+ (loader.VisualSettings.ScreenSpaceDrawQuad.BottomRight - loader.VisualSettings.ScreenSpaceDrawQuad.TopLeft)
@ -241,13 +264,13 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestMutedNotificationMasterVolume()
{
addVolumeSteps("master volume", () => audioManager.Volume.Value = 0, () => audioManager.Volume.IsDefault);
addVolumeSteps("master volume", () => audioManager.Volume.Value = 0, () => audioManager.Volume.Value == 0.5);
}
[Test]
public void TestMutedNotificationTrackVolume()
{
addVolumeSteps("music volume", () => audioManager.VolumeTrack.Value = 0, () => audioManager.VolumeTrack.IsDefault);
addVolumeSteps("music volume", () => audioManager.VolumeTrack.Value = 0, () => audioManager.VolumeTrack.Value == 0.5);
}
[Test]
@ -274,19 +297,16 @@ namespace osu.Game.Tests.Visual.Gameplay
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();
saveVolumes();
InputManager.MoveMouseTo(notification);
InputManager.Click(MouseButton.Left);
});
AddAssert("check for notification", () => notificationOverlay.UnreadCount.Value, () => Is.EqualTo(1));
clickNotification();
AddAssert("check " + volumeName, assert);
restoreVolumes();
AddUntilStep("wait for player load", () => player.IsLoaded);
}
@ -294,6 +314,9 @@ namespace osu.Game.Tests.Visual.Gameplay
[TestCase(false)]
public void TestEpilepsyWarning(bool warning)
{
saveVolumes();
setFullVolume();
AddStep("change epilepsy warning", () => epilepsyWarning = warning);
AddStep("load dummy beatmap", () => resetPlayer(false));
@ -306,38 +329,16 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("sound volume decreased", () => Beatmap.Value.Track.AggregateVolume.Value == 0.25);
AddUntilStep("sound volume restored", () => Beatmap.Value.Track.AggregateVolume.Value == 1);
}
}
[TestCase(false, 1.0, false)] // not charging, above cutoff --> no warning
[TestCase(true, 0.1, false)] // charging, below cutoff --> no warning
[TestCase(false, 0.25, true)] // not charging, at cutoff --> warning
public void TestLowBatteryNotification(bool isCharging, double chargeLevel, bool shouldWarn)
{
AddStep("reset notification lock", () => sessionStatics.GetBindable<bool>(Static.LowBatteryNotificationShownOnce).Value = false);
// set charge status and level
AddStep("load player", () => resetPlayer(false, () =>
{
batteryInfo.SetCharging(isCharging);
batteryInfo.SetChargeLevel(chargeLevel);
}));
AddUntilStep("wait for player", () => player?.LoadState == LoadState.Ready);
AddAssert($"notification {(shouldWarn ? "triggered" : "not triggered")}", () => notificationOverlay.UnreadCount.Value == (shouldWarn ? 1 : 0));
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);
});
AddUntilStep("wait for player load", () => player.IsLoaded);
restoreVolumes();
}
[Test]
public void TestEpilepsyWarningEarlyExit()
{
saveVolumes();
setFullVolume();
AddStep("set epilepsy warning", () => epilepsyWarning = true);
AddStep("load dummy beatmap", () => resetPlayer(false));
@ -350,6 +351,102 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("warning is hidden", () => getWarning().State.Value == Visibility.Hidden);
AddUntilStep("sound volume restored", () => Beatmap.Value.Track.AggregateVolume.Value == 1);
restoreVolumes();
}
[TestCase(true, 1.0, false)] // on battery, above cutoff --> no warning
[TestCase(false, 0.1, false)] // not on battery, below cutoff --> no warning
[TestCase(true, 0.25, true)] // on battery, at cutoff --> warning
[TestCase(true, null, false)] // on battery, level unknown --> no warning
public void TestLowBatteryNotification(bool onBattery, double? chargeLevel, bool shouldWarn)
{
AddStep("reset notification lock", () => sessionStatics.GetBindable<bool>(Static.LowBatteryNotificationShownOnce).Value = false);
// set charge status and level
AddStep("load player", () => resetPlayer(false, () =>
{
batteryInfo.SetOnBattery(onBattery);
batteryInfo.SetChargeLevel(chargeLevel);
}));
AddUntilStep("wait for player", () => player?.LoadState == LoadState.Ready);
if (shouldWarn)
clickNotification();
else
AddAssert("notification not triggered", () => notificationOverlay.UnreadCount.Value == 0);
AddUntilStep("wait for player load", () => player.IsLoaded);
}
private void restoreVolumes()
{
AddStep("restore previous volumes", () =>
{
audioManager.VolumeTrack.Value = savedTrackVolume;
audioManager.Volume.Value = savedMasterVolume;
volumeOverlay.IsMuted.Value = savedMutedState;
});
}
private void setFullVolume()
{
AddStep("set volumes to 100%", () =>
{
audioManager.VolumeTrack.Value = 1;
audioManager.Volume.Value = 1;
volumeOverlay.IsMuted.Value = false;
});
}
private void saveVolumes()
{
AddStep("save previous volumes", () =>
{
savedTrackVolume = audioManager.VolumeTrack.Value;
savedMasterVolume = audioManager.Volume.Value;
savedMutedState = volumeOverlay.IsMuted.Value;
});
}
[Test]
public void TestQuickRetry()
{
TestPlayer getCurrentPlayer() => loader.CurrentPlayer as TestPlayer;
bool checkSkipButtonVisible() => player.ChildrenOfType<SkipOverlay>().FirstOrDefault()?.IsButtonVisible == true;
TestPlayer previousPlayer = null;
AddStep("load dummy beatmap", () => resetPlayer(false));
AddUntilStep("wait for current", () => getCurrentPlayer()?.IsCurrentScreen() == true);
AddStep("store previous player", () => previousPlayer = getCurrentPlayer());
AddStep("Restart map normally", () => getCurrentPlayer().Restart());
AddUntilStep("wait for load", () => getCurrentPlayer()?.LoadedBeatmapSuccessfully == true);
AddUntilStep("restart completed", () => getCurrentPlayer() != null && getCurrentPlayer() != previousPlayer);
AddStep("store previous player", () => previousPlayer = getCurrentPlayer());
AddUntilStep("skip button visible", checkSkipButtonVisible);
AddStep("press quick retry key", () => InputManager.PressKey(Key.Tilde));
AddUntilStep("restart completed", () => getCurrentPlayer() != null && getCurrentPlayer() != previousPlayer);
AddStep("release quick retry key", () => InputManager.ReleaseKey(Key.Tilde));
AddUntilStep("wait for player", () => getCurrentPlayer()?.LoadState == LoadState.Ready);
AddUntilStep("time reached zero", () => getCurrentPlayer()?.GameplayClockContainer.CurrentTime > 0);
AddUntilStep("skip button not visible", () => !checkSkipButtonVisible());
}
private void clickNotification()
{
Notification notification = null;
AddUntilStep("wait for notification", () => (notification = notificationOverlay.ChildrenOfType<Notification>().FirstOrDefault()) != null);
AddStep("open notification overlay", () => notificationOverlay.Show());
AddStep("click notification", () => notification.TriggerClick());
}
private EpilepsyWarning getWarning() => loader.ChildrenOfType<EpilepsyWarning>().SingleOrDefault();
@ -373,7 +470,7 @@ namespace osu.Game.Tests.Visual.Gameplay
public override string Name => string.Empty;
public override string Acronym => string.Empty;
public override double ScoreMultiplier => 1;
public override string Description => string.Empty;
public override LocalisableString Description => string.Empty;
public bool Applied { get; private set; }
@ -408,19 +505,19 @@ namespace osu.Game.Tests.Visual.Gameplay
/// <inheritdoc/>
private class LocalBatteryInfo : BatteryInfo
{
private bool isCharging = true;
private double chargeLevel = 1;
private bool onBattery;
private double? chargeLevel;
public override bool IsCharging => isCharging;
public override bool OnBattery => onBattery;
public override double ChargeLevel => chargeLevel;
public override double? ChargeLevel => chargeLevel;
public void SetCharging(bool value)
public void SetOnBattery(bool value)
{
isCharging = value;
onBattery = value;
}
public void SetChargeLevel(double value)
public void SetChargeLevel(double? value)
{
chargeLevel = value;
}

View File

@ -1,20 +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.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Extensions;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics.Containers;
using osu.Framework.Platform;
using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Graphics.Containers;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using osu.Game.Screens.Play;
using osu.Game.Screens.Ranking;
using osu.Game.Tests.Resources;
@ -31,8 +38,8 @@ namespace osu.Game.Tests.Visual.Gameplay
private void load(GameHost host, AudioManager audio)
{
Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default));
Dependencies.Cache(new ScoreManager(rulesets, () => beatmaps, LocalStorage, Realm, Scheduler));
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default));
Dependencies.Cache(new ScoreManager(rulesets, () => beatmaps, LocalStorage, Realm, API));
Dependencies.Cache(Realm);
}
@ -57,13 +64,79 @@ namespace osu.Game.Tests.Visual.Gameplay
protected override bool HasCustomSteps => true;
protected override bool AllowFail => false;
protected override bool AllowFail => allowFail;
private bool allowFail;
[SetUp]
public void SetUp()
{
allowFail = false;
customRuleset = null;
}
[Test]
public void TestSaveFailedReplay()
{
AddStep("allow fail", () => allowFail = true);
CreateTest();
AddUntilStep("fail screen displayed", () => Player.ChildrenOfType<FailOverlay>().First().State.Value == Visibility.Visible);
AddUntilStep("wait for button clickable", () => Player.ChildrenOfType<SaveFailedScoreButton>().First().ChildrenOfType<OsuClickableContainer>().First().Enabled.Value);
AddUntilStep("score not in database", () => Realm.Run(r => r.Find<ScoreInfo>(Player.Score.ScoreInfo.ID) == null));
AddStep("click save button", () => Player.ChildrenOfType<SaveFailedScoreButton>().First().ChildrenOfType<OsuClickableContainer>().First().TriggerClick());
AddUntilStep("score in database", () => Realm.Run(r => r.Find<ScoreInfo>(Player.Score.ScoreInfo.ID) != null));
}
[Test]
public void TestLastPlayedUpdated()
{
DateTimeOffset? getLastPlayed() => Realm.Run(r => r.Find<BeatmapInfo>(Beatmap.Value.BeatmapInfo.ID)?.LastPlayed);
AddAssert("last played is null", () => getLastPlayed() == null);
CreateTest();
AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning);
AddUntilStep("wait for last played to update", () => getLastPlayed() != null);
}
[Test]
public void TestModReferenceNotRetained()
{
AddStep("allow fail", () => allowFail = false);
Mod[] originalMods = { new OsuModDaycore { SpeedChange = { Value = 0.8 } } };
Mod[] playerMods = null!;
AddStep("load player with mods", () => LoadPlayer(originalMods));
AddUntilStep("player loaded", () => Player.IsLoaded && Player.Alpha == 1);
AddStep("get mods at start of gameplay", () => playerMods = Player.Score.ScoreInfo.Mods.ToArray());
// Player creates new instance of mods during load.
AddAssert("player score has copied mods", () => playerMods.First(), () => Is.Not.SameAs(originalMods.First()));
AddAssert("player score has matching mods", () => playerMods.First(), () => Is.EqualTo(originalMods.First()));
AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning);
AddStep("seek to completion", () => Player.GameplayClockContainer.Seek(Player.DrawableRuleset.Objects.Last().GetEndTime()));
AddUntilStep("results displayed", () => Player.GetChildScreen() is ResultsScreen);
// Player creates new instance of mods after gameplay to ensure any runtime references to drawables etc. are not retained.
AddAssert("results screen score has copied mods", () => (Player.GetChildScreen() as ResultsScreen)?.Score.Mods.First(), () => Is.Not.SameAs(playerMods.First()));
AddAssert("results screen score has matching", () => (Player.GetChildScreen() as ResultsScreen)?.Score.Mods.First(), () => Is.EqualTo(playerMods.First()));
AddUntilStep("score in database", () => Realm.Run(r => r.Find<ScoreInfo>(Player.Score.ScoreInfo.ID) != null));
AddUntilStep("databased score has correct mods", () => Realm.Run(r => r.Find<ScoreInfo>(Player.Score.ScoreInfo.ID)).Mods.First(), () => Is.EqualTo(playerMods.First()));
}
[Test]
public void TestScoreStoredLocally()
{
AddStep("set no custom ruleset", () => customRuleset = null);
CreateTest();
AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning);

View File

@ -239,7 +239,7 @@ namespace osu.Game.Tests.Visual.Gameplay
createPlayerTest(false, r =>
{
var beatmap = createTestBeatmap(r);
beatmap.BeatmapInfo.OnlineID = -1;
beatmap.BeatmapInfo.ResetOnlineInfo();
return beatmap;
});

View File

@ -158,21 +158,24 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("clean up", () => drawableRuleset.NewResult -= onNewResult);
}
private void createTest(IBeatmap beatmap, int poolSize, Func<IFrameBasedClock> createClock = null) => AddStep("create test", () =>
private void createTest(IBeatmap beatmap, int poolSize, Func<IFrameBasedClock> createClock = null)
{
var ruleset = new TestPoolingRuleset();
drawableRuleset = (TestDrawablePoolingRuleset)ruleset.CreateDrawableRulesetWith(CreateWorkingBeatmap(beatmap).GetPlayableBeatmap(ruleset.RulesetInfo));
drawableRuleset.FrameStablePlayback = true;
drawableRuleset.PoolSize = poolSize;
Child = new Container
AddStep("create test", () =>
{
RelativeSizeAxes = Axes.Both,
Clock = createClock?.Invoke() ?? new FramedOffsetClock(Clock, false) { Offset = -Clock.CurrentTime },
Child = drawableRuleset
};
});
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

View File

@ -7,7 +7,6 @@ using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Game.Online;
using osu.Game.Online.API.Requests.Responses;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions;
@ -15,7 +14,6 @@ using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Database;
using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
using osu.Game.Scoring;
using osu.Game.Screens.Ranking;
@ -30,9 +28,6 @@ namespace osu.Game.Tests.Visual.Gameplay
{
private const long online_score_id = 2553163309;
[Resolved]
private RulesetStore rulesets { get; set; }
private TestReplayDownloadButton downloadButton;
[Resolved]
@ -207,25 +202,22 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("wait for load", () => downloadButton.IsLoaded);
AddAssert("state is not downloaded", () => downloadButton.State.Value == DownloadState.NotDownloaded);
AddAssert("state is unknown", () => downloadButton.State.Value == DownloadState.Unknown);
AddAssert("button is not enabled", () => !downloadButton.ChildrenOfType<DownloadButton>().First().Enabled.Value);
}
private ScoreInfo getScoreInfo(bool replayAvailable, bool hasOnlineId = true)
private ScoreInfo getScoreInfo(bool replayAvailable, bool hasOnlineId = true) => new ScoreInfo
{
return new APIScore
OnlineID = hasOnlineId ? online_score_id : 0,
Ruleset = new OsuRuleset().RulesetInfo,
BeatmapInfo = beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps.First(),
Hash = replayAvailable ? "online" : string.Empty,
User = new APIUser
{
OnlineID = hasOnlineId ? online_score_id : 0,
RulesetID = 0,
Beatmap = CreateAPIBeatmapSet(new OsuRuleset().RulesetInfo).Beatmaps.First(),
HasReplay = replayAvailable,
User = new APIUser
{
Id = 39828,
Username = @"WubWoofWolf",
}
}.CreateScoreInfo(rulesets, beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps.First());
}
Id = 39828,
Username = @"WubWoofWolf",
}
};
private class TestReplayDownloadButton : ReplayDownloadButton
{

View File

@ -3,10 +3,10 @@
#nullable disable
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.Shapes;
@ -40,8 +40,7 @@ namespace osu.Game.Tests.Visual.Gameplay
private TestReplayRecorder recorder;
[Cached]
private GameplayState gameplayState = TestGameplayState.Create(new OsuRuleset());
private GameplayState gameplayState;
[SetUpSteps]
public void SetUpSteps()
@ -52,81 +51,15 @@ namespace osu.Game.Tests.Visual.Gameplay
{
replay = new Replay();
Add(new GridContainer
gameplayState = TestGameplayState.Create(new OsuRuleset());
gameplayState.Score.Replay = replay;
Child = new DependencyProvidingContainer
{
RelativeSizeAxes = Axes.Both,
Content = new[]
{
new Drawable[]
{
recordingManager = new TestRulesetInputManager(TestCustomisableModRuleset.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique)
{
Recorder = recorder = new TestReplayRecorder(new Score
{
Replay = replay,
ScoreInfo =
{
BeatmapInfo = gameplayState.Beatmap.BeatmapInfo,
Ruleset = new OsuRuleset().RulesetInfo,
}
})
{
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(TestCustomisableModRuleset.CreateTestRulesetInfo(), 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()
}
},
}
}
}
});
CachedDependencies = new (Type, object)[] { (typeof(GameplayState), gameplayState) },
Child = createContent(),
};
});
}
@ -203,6 +136,74 @@ namespace osu.Game.Tests.Visual.Gameplay
recorder = null;
}
private Drawable createContent() => new GridContainer
{
RelativeSizeAxes = Axes.Both,
Content = new[]
{
new Drawable[]
{
recordingManager = new TestRulesetInputManager(TestCustomisableModRuleset.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique)
{
Recorder = recorder = new TestReplayRecorder(gameplayState.Score)
{
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(TestCustomisableModRuleset.CreateTestRulesetInfo(), 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()
}
},
}
}
}
};
public class TestFramedReplayInputHandler : FramedReplayInputHandler<TestReplayFrame>
{
public TestFramedReplayInputHandler(Replay replay)

View File

@ -11,8 +11,10 @@ using osu.Framework.Allocation;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Primitives;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Threading;
using osu.Framework.Timing;
using osu.Framework.Utils;
using osu.Game.Configuration;
using osu.Game.Rulesets.Mods;
@ -167,14 +169,39 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("add control points", () => addControlPoints(testControlPoints, Time.Current));
}
private void addHitObject(double time)
[Test]
public void TestVeryFlowScroll()
{
const double long_time_range = 100000;
var manualClock = new ManualClock();
AddStep("set manual clock", () =>
{
manualClock.CurrentTime = 0;
scrollContainers.ForEach(c => c.Clock = new FramedClock(manualClock));
setScrollAlgorithm(ScrollVisualisationMethod.Constant);
scrollContainers.ForEach(c => c.TimeRange = long_time_range);
});
AddStep("add hit objects", () =>
{
addHitObject(long_time_range);
addHitObject(long_time_range + 100, 250);
});
AddAssert("hit objects are alive", () => playfields.All(p => p.HitObjectContainer.AliveObjects.Count() == 2));
}
private void addHitObject(double time, float size = 75)
{
playfields.ForEach(p =>
{
var hitObject = new TestDrawableHitObject(time);
setAnchor(hitObject, p);
var hitObject = new TestHitObject(size) { StartTime = time };
var drawable = new TestDrawableHitObject(hitObject);
p.Add(hitObject);
setAnchor(drawable, p);
p.Add(drawable);
});
}
@ -187,7 +214,7 @@ namespace osu.Game.Tests.Visual.Gameplay
private void addControlPoints(IList<MultiplierControlPoint> controlPoints, double sequenceStartTime)
{
controlPoints.ForEach(point => point.StartTime += sequenceStartTime);
controlPoints.ForEach(point => point.Time += sequenceStartTime);
scrollContainers.ForEach(container =>
{
@ -197,7 +224,7 @@ namespace osu.Game.Tests.Visual.Gameplay
foreach (var playfield in playfields)
{
foreach (var controlPoint in controlPoints)
playfield.Add(createDrawablePoint(playfield, controlPoint.StartTime));
playfield.Add(createDrawablePoint(playfield, controlPoint.Time));
}
}
@ -248,6 +275,8 @@ namespace osu.Game.Tests.Visual.Gameplay
}
};
}
protected override ScrollingHitObjectContainer CreateScrollingHitObjectContainer() => new TestScrollingHitObjectContainer();
}
private class TestDrawableControlPoint : DrawableHitObject<HitObject>
@ -281,22 +310,41 @@ namespace osu.Game.Tests.Visual.Gameplay
}
}
private class TestDrawableHitObject : DrawableHitObject<HitObject>
private class TestHitObject : HitObject
{
public TestDrawableHitObject(double time)
: base(new HitObject { StartTime = time, HitWindows = HitWindows.Empty })
{
Origin = Anchor.Custom;
OriginPosition = new Vector2(75 / 4.0f);
public readonly float Size;
AutoSizeAxes = Axes.Both;
public TestHitObject(float size)
{
Size = size;
}
}
private class TestDrawableHitObject : DrawableHitObject<TestHitObject>
{
public TestDrawableHitObject(TestHitObject hitObject)
: base(hitObject)
{
Origin = Anchor.Centre;
Size = new Vector2(hitObject.Size);
AddInternal(new Box
{
Size = new Vector2(75),
RelativeSizeAxes = Axes.Both,
Colour = new Color4(RNG.NextSingle(), RNG.NextSingle(), RNG.NextSingle(), 1)
});
}
}
private class TestScrollingHitObjectContainer : ScrollingHitObjectContainer
{
protected override RectangleF GetConservativeBoundingBox(HitObjectLifetimeEntry entry)
{
if (entry.HitObject is TestHitObject testObject)
return new RectangleF().Inflate(testObject.Size / 2);
return base.GetConservativeBoundingBox(entry);
}
}
}
}

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.
#nullable disable
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
@ -14,6 +12,7 @@ using osu.Game.Overlays.Settings;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Play.HUD.HitErrorMeters;
using osu.Game.Skinning;
using osu.Game.Skinning.Editor;
using osuTK.Input;
@ -21,7 +20,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{
public class TestSceneSkinEditor : PlayerTestScene
{
private SkinEditor skinEditor;
private SkinEditor? skinEditor;
protected override bool Autoplay => true;
@ -33,35 +32,41 @@ namespace osu.Game.Tests.Visual.Gameplay
{
base.SetUpSteps();
AddUntilStep("wait for hud load", () => Player.ChildrenOfType<SkinnableTargetContainer>().All(c => c.ComponentsLoaded));
AddStep("reload skin editor", () =>
{
skinEditor?.Expire();
Player.ScaleTo(0.4f);
LoadComponentAsync(skinEditor = new SkinEditor(Player), Add);
});
AddUntilStep("wait for loaded", () => skinEditor.IsLoaded);
AddUntilStep("wait for loaded", () => skinEditor!.IsLoaded);
}
[Test]
public void TestToggleEditor()
{
AddToggleStep("toggle editor visibility", _ => skinEditor.ToggleVisibility());
AddToggleStep("toggle editor visibility", _ => skinEditor!.ToggleVisibility());
}
[Test]
public void TestEditComponent()
{
BarHitErrorMeter hitErrorMeter = null;
BarHitErrorMeter hitErrorMeter = null!;
AddStep("select bar hit error blueprint", () =>
{
var blueprint = skinEditor.ChildrenOfType<SkinBlueprint>().First(b => b.Item is BarHitErrorMeter);
hitErrorMeter = (BarHitErrorMeter)blueprint.Item;
skinEditor.SelectedComponents.Clear();
skinEditor!.SelectedComponents.Clear();
skinEditor.SelectedComponents.Add(blueprint.Item);
});
AddStep("move by keyboard", () => InputManager.Key(Key.Right));
AddAssert("hitErrorMeter moved", () => hitErrorMeter.X != 0);
AddAssert("value is default", () => hitErrorMeter.JudgementLineThickness.IsDefault);
AddStep("hover first slider", () =>

View File

@ -29,8 +29,8 @@ namespace osu.Game.Tests.Visual.Gameplay
[Cached]
private GameplayState gameplayState = TestGameplayState.Create(new OsuRuleset());
[Cached]
private readonly GameplayClock gameplayClock = new GameplayClock(new FramedClock());
[Cached(typeof(IGameplayClock))]
private readonly IGameplayClock gameplayClock = new GameplayClockContainer(new FramedClock());
[SetUpSteps]
public void SetUpSteps()

View File

@ -10,6 +10,7 @@ using osu.Framework.Testing;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Play.HUD;
using osu.Game.Skinning;
namespace osu.Game.Tests.Visual.Gameplay
{

View File

@ -13,7 +13,6 @@ 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;

View File

@ -36,8 +36,8 @@ namespace osu.Game.Tests.Visual.Gameplay
[Cached]
private GameplayState gameplayState = TestGameplayState.Create(new OsuRuleset());
[Cached]
private readonly GameplayClock gameplayClock = new GameplayClock(new FramedClock());
[Cached(typeof(IGameplayClock))]
private readonly IGameplayClock gameplayClock = new GameplayClockContainer(new FramedClock());
private IEnumerable<HUDOverlay> hudOverlays => CreatedDrawables.OfType<HUDOverlay>();

View File

@ -13,7 +13,6 @@ 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;

View File

@ -6,6 +6,7 @@
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;
@ -22,25 +23,23 @@ namespace osu.Game.Tests.Visual.Gameplay
private double increment;
private GameplayClockContainer gameplayClockContainer;
private GameplayClock gameplayClock;
private IFrameBasedClock gameplayClock;
private const double skip_time = 6000;
[SetUp]
public void SetUp() => Schedule(() =>
private void createTest(double skipTime = skip_time) => AddStep("create test", () =>
{
requestCount = 0;
increment = skip_time;
var working = CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo));
working.LoadTrack();
Child = gameplayClockContainer = new MasterGameplayClockContainer(working, 0)
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
skip = new TestSkipOverlay(skip_time)
skip = new TestSkipOverlay(skipTime)
{
RequestSkip = () =>
{
@ -52,12 +51,28 @@ namespace osu.Game.Tests.Visual.Gameplay
};
gameplayClockContainer.Start();
gameplayClock = gameplayClockContainer.GameplayClock;
gameplayClock = gameplayClockContainer;
});
[Test]
public void TestSkipTimeZero()
{
createTest(0);
AddUntilStep("wait for skip overlay expired", () => !skip.IsAlive);
}
[Test]
public void TestSkipTimeEqualToSkip()
{
createTest(MasterGameplayClockContainer.MINIMUM_SKIP_TIME);
AddUntilStep("wait for skip overlay expired", () => !skip.IsAlive);
}
[Test]
public void TestFadeOnIdle()
{
createTest();
AddStep("move mouse", () => InputManager.MoveMouseTo(Vector2.Zero));
AddUntilStep("fully visible", () => skip.FadingContent.Alpha == 1);
AddUntilStep("wait for fade", () => skip.FadingContent.Alpha < 1);
@ -70,6 +85,8 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestClickableAfterFade()
{
createTest();
AddStep("move mouse", () => InputManager.MoveMouseTo(skip.ScreenSpaceDrawQuad.Centre));
AddUntilStep("wait for fade", () => skip.FadingContent.Alpha == 0);
AddStep("click", () => InputManager.Click(MouseButton.Left));
@ -79,6 +96,8 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestClickOnlyActuatesOnce()
{
createTest();
AddStep("move mouse", () => InputManager.MoveMouseTo(skip.ScreenSpaceDrawQuad.Centre));
AddStep("click", () =>
{
@ -94,6 +113,8 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestClickOnlyActuatesMultipleTimes()
{
createTest();
AddStep("set increment lower", () => increment = 3000);
AddStep("move mouse", () => InputManager.MoveMouseTo(skip.ScreenSpaceDrawQuad.Centre));
AddStep("click", () => InputManager.Click(MouseButton.Left));
@ -106,6 +127,8 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestDoesntFadeOnMouseDown()
{
createTest();
AddStep("move mouse", () => InputManager.MoveMouseTo(skip.ScreenSpaceDrawQuad.Centre));
AddStep("button down", () => InputManager.PressButton(MouseButton.Left));
AddUntilStep("wait for overlay disappear", () => !skip.OverlayContent.IsPresent);

View File

@ -0,0 +1,101 @@
// 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.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Configuration;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using osu.Game.Screens.Play.HUD;
namespace osu.Game.Tests.Visual.Gameplay
{
public class TestSceneSoloGameplayLeaderboard : OsuTestScene
{
[Cached]
private readonly ScoreProcessor scoreProcessor = new ScoreProcessor(new OsuRuleset());
private readonly BindableList<ScoreInfo> scores = new BindableList<ScoreInfo>();
private readonly Bindable<bool> configVisibility = new Bindable<bool>();
private SoloGameplayLeaderboard leaderboard = null!;
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
{
config.BindWith(OsuSetting.GameplayLeaderboard, configVisibility);
}
[SetUpSteps]
public void SetUpSteps()
{
AddStep("clear scores", () => scores.Clear());
AddStep("create component", () =>
{
var trackingUser = new APIUser
{
Username = "local user",
Id = 2,
};
Child = leaderboard = new SoloGameplayLeaderboard(trackingUser)
{
Scores = { BindTarget = scores },
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
AlwaysVisible = { Value = false },
Expanded = { Value = true },
};
});
AddStep("add scores", () => scores.AddRange(createSampleScores()));
}
[Test]
public void TestLocalUser()
{
AddSliderStep("score", 0, 1000000, 500000, v => scoreProcessor.TotalScore.Value = v);
AddSliderStep("accuracy", 0f, 1f, 0.5f, v => scoreProcessor.Accuracy.Value = v);
AddSliderStep("combo", 0, 10000, 0, v => scoreProcessor.HighestCombo.Value = v);
AddStep("toggle expanded", () => leaderboard.Expanded.Value = !leaderboard.Expanded.Value);
}
[Test]
public void TestVisibility()
{
AddStep("set config visible true", () => configVisibility.Value = true);
AddUntilStep("leaderboard visible", () => leaderboard.Alpha == 1);
AddStep("set config visible false", () => configVisibility.Value = false);
AddUntilStep("leaderboard not visible", () => leaderboard.Alpha == 0);
AddStep("set always visible", () => leaderboard.AlwaysVisible.Value = true);
AddUntilStep("leaderboard visible", () => leaderboard.Alpha == 1);
AddStep("set config visible true", () => configVisibility.Value = true);
AddAssert("leaderboard still visible", () => leaderboard.Alpha == 1);
}
private static List<ScoreInfo> createSampleScores()
{
return new[]
{
new ScoreInfo { User = new APIUser { Username = @"peppy" }, TotalScore = RNG.Next(500000, 1000000) },
new ScoreInfo { User = new APIUser { Username = @"smoogipoo" }, TotalScore = RNG.Next(500000, 1000000) },
new ScoreInfo { User = new APIUser { Username = @"spaceman_atlas" }, TotalScore = RNG.Next(500000, 1000000) },
new ScoreInfo { User = new APIUser { Username = @"frenzibyte" }, TotalScore = RNG.Next(500000, 1000000) },
new ScoreInfo { User = new APIUser { Username = @"Susko3" }, TotalScore = RNG.Next(500000, 1000000) },
}.Concat(Enumerable.Range(0, 50).Select(i => new ScoreInfo { User = new APIUser { Username = $"User {i + 1}" }, TotalScore = 1000000 - i * 10000 })).ToList();
}
}
}

View File

@ -1,161 +1,77 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System;
using System.Collections.Generic;
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.Testing;
using osu.Framework.Utils;
using osu.Framework.Timing;
using osu.Game.Graphics;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Play;
using osu.Game.Screens.Play.HUD;
using osu.Game.Skinning;
namespace osu.Game.Tests.Visual.Gameplay
{
[TestFixture]
public class TestSceneSongProgress : OsuTestScene
public class TestSceneSongProgress : SkinnableHUDComponentTestScene
{
private SongProgress progress;
private TestSongProgressGraph graph;
private readonly Container progressContainer;
private GameplayClockContainer gameplayClockContainer = null!;
private readonly StopwatchClock clock;
private readonly FramedClock framedClock;
private const double skip_target_time = -2000;
[Cached]
private readonly GameplayClock gameplayClock;
public TestSceneSongProgress()
[BackgroundDependencyLoader]
private void load()
{
clock = new StopwatchClock();
gameplayClock = new GameplayClock(framedClock = new FramedClock(clock));
Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
Add(progressContainer = new Container
{
RelativeSizeAxes = Axes.X,
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
Height = 100,
Y = -100,
Child = new Box
{
RelativeSizeAxes = Axes.Both,
Colour = OsuColour.Gray(1),
}
});
Add(gameplayClockContainer = new MasterGameplayClockContainer(Beatmap.Value, skip_target_time));
Dependencies.CacheAs<IGameplayClock>(gameplayClockContainer);
}
[SetUpSteps]
public void SetupSteps()
{
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);
AddStep("reset clock", () => gameplayClockContainer.Reset());
AddStep("set hit objects", setHitObjects);
}
[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);
AddStep("seek to intro", () => gameplayClockContainer.Seek(skip_target_time));
AddStep("start", gameplayClockContainer.Start);
AddStep("stop", gameplayClockContainer.Stop);
}
private void displayRandomValues()
[Test]
public void TestToggleSeeking()
{
var objects = new List<HitObject>();
for (double i = 0; i < 5000; i += RNG.NextDouble() * 10 + i / 1000)
objects.Add(new HitObject { StartTime = i });
void applyToDefaultProgress(Action<DefaultSongProgress> action) =>
this.ChildrenOfType<DefaultSongProgress>().ForEach(action);
replaceObjects(objects);
AddStep("allow seeking", () => applyToDefaultProgress(s => s.AllowSeeking.Value = true));
AddStep("hide graph", () => applyToDefaultProgress(s => s.ShowGraph.Value = false));
AddStep("disallow seeking", () => applyToDefaultProgress(s => s.AllowSeeking.Value = false));
AddStep("allow seeking", () => applyToDefaultProgress(s => s.AllowSeeking.Value = true));
AddStep("show graph", () => applyToDefaultProgress(s => s.ShowGraph.Value = true));
}
private void displayMaxValues()
private void setHitObjects()
{
var objects = new List<HitObject>();
for (double i = 0; i < 5000; i++)
objects.Add(new HitObject { StartTime = i });
replaceObjects(objects);
this.ChildrenOfType<SongProgress>().ForEach(progress => progress.Objects = objects);
}
private void replaceObjects(List<HitObject> objects)
{
progress.Objects = objects;
graph.Objects = objects;
protected override Drawable CreateDefaultImplementation() => new DefaultSongProgress();
progress.RequestSeek = pos => clock.Seek(pos);
}
protected override void Update()
{
base.Update();
framedClock.ProcessFrame();
}
private class TestSongProgressGraph : SongProgressGraph
{
public int CreationCount { get; private set; }
protected override void RecreateGraph()
{
base.RecreateGraph();
CreationCount++;
}
}
protected override Drawable CreateLegacyImplementation() => new LegacySongProgress();
}
}

View File

@ -0,0 +1,73 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Rulesets.Objects;
using osu.Game.Screens.Play.HUD;
namespace osu.Game.Tests.Visual.Gameplay
{
[TestFixture]
public class TestSceneSongProgressGraph : OsuTestScene
{
private TestSongProgressGraph graph;
[SetUpSteps]
public void SetupSteps()
{
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,
});
});
}
[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);
}
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 });
graph.Objects = objects;
}
private class TestSongProgressGraph : SongProgressGraph
{
public int CreationCount { get; private set; }
protected override void RecreateGraph()
{
base.RecreateGraph();
CreationCount++;
}
}
}
}

View File

@ -85,7 +85,7 @@ namespace osu.Game.Tests.Visual.Gameplay
sendFrames(startTime: gameplay_start);
AddAssert("time is greater than seek target", () => currentFrameStableTime > gameplay_start);
AddAssert("time is greater than seek target", () => currentFrameStableTime, () => Is.GreaterThan(gameplay_start));
}
/// <summary>
@ -119,7 +119,7 @@ namespace osu.Game.Tests.Visual.Gameplay
waitForPlayer();
AddUntilStep("state is playing", () => spectatorClient.WatchedUserStates[streamingUser.Id].State == SpectatedUserState.Playing);
AddAssert("time is greater than seek target", () => currentFrameStableTime > gameplay_start);
AddAssert("time is greater than seek target", () => currentFrameStableTime, () => Is.GreaterThan(gameplay_start));
}
[Test]
@ -147,7 +147,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("wait for frame starvation", () => replayHandler.WaitingForFrame);
checkPaused(true);
AddAssert("time advanced", () => currentFrameStableTime > pausedTime);
AddAssert("time advanced", () => currentFrameStableTime, () => Is.GreaterThan(pausedTime));
}
[Test]
@ -176,7 +176,7 @@ namespace osu.Game.Tests.Visual.Gameplay
sendFrames(300);
AddUntilStep("playing from correct point in time", () => player.ChildrenOfType<DrawableRuleset>().First().FrameStableClock.CurrentTime > 30000);
AddUntilStep("playing from correct point in time", () => player.ChildrenOfType<DrawableRuleset>().First().FrameStableClock.CurrentTime, () => Is.GreaterThan(30000));
}
[Test]
@ -363,7 +363,7 @@ namespace osu.Game.Tests.Visual.Gameplay
private Player player => Stack.CurrentScreen as Player;
private double currentFrameStableTime
=> player.ChildrenOfType<FrameStabilityContainer>().First().FrameStableClock.CurrentTime;
=> player.ChildrenOfType<FrameStabilityContainer>().First().CurrentTime;
private void waitForPlayer() => AddUntilStep("wait for player", () => (Stack.CurrentScreen as Player)?.IsLoaded == true);

View File

@ -5,6 +5,7 @@
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Screens;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Spectator;
@ -43,11 +44,26 @@ namespace osu.Game.Tests.Visual.Gameplay
AddAssert("spectator client sent correct ruleset", () => spectatorClient.WatchedUserStates[dummy_user_id].RulesetID == Ruleset.Value.OnlineID);
}
[Test]
public void TestRestart()
{
AddAssert("spectator client sees playing state", () => spectatorClient.WatchedUserStates[dummy_user_id].State == SpectatedUserState.Playing);
AddStep("exit player", () => Player.Exit());
AddStep("reload player", LoadPlayer);
AddUntilStep("wait for player load", () => Player.IsLoaded && Player.Alpha == 1);
AddAssert("spectator client sees playing state", () => spectatorClient.WatchedUserStates[dummy_user_id].State == SpectatedUserState.Playing);
AddWaitStep("wait", 5);
AddUntilStep("spectator client still sees playing state", () => spectatorClient.WatchedUserStates[dummy_user_id].State == SpectatedUserState.Playing);
}
public override void TearDownSteps()
{
base.TearDownSteps();
AddStep("stop watching user", () => spectatorClient.StopWatchingUser(dummy_user_id));
AddStep("remove test spectator client", () => Remove(spectatorClient));
AddStep("remove test spectator client", () => Remove(spectatorClient, false));
}
}
}

View File

@ -27,7 +27,6 @@ using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.Replays.Types;
using osu.Game.Rulesets.UI;
using osu.Game.Scoring;
using osu.Game.Screens.Play;
using osu.Game.Tests.Gameplay;
using osu.Game.Tests.Mods;
using osu.Game.Tests.Visual.Spectator;
@ -41,16 +40,12 @@ namespace osu.Game.Tests.Visual.Gameplay
private TestRulesetInputManager playbackManager;
private TestRulesetInputManager recordingManager;
private Replay replay;
private Score recordingScore;
private Replay playbackReplay;
private TestSpectatorClient spectatorClient;
private ManualClock manualClock;
private TestReplayRecorder recorder;
private OsuSpriteText latencyDisplay;
private TestFramedReplayInputHandler replayHandler;
[SetUpSteps]
@ -58,7 +53,16 @@ namespace osu.Game.Tests.Visual.Gameplay
{
AddStep("Setup containers", () =>
{
replay = new Replay();
recordingScore = new Score
{
ScoreInfo =
{
BeatmapInfo = new BeatmapInfo(),
Ruleset = new OsuRuleset().RulesetInfo,
}
};
playbackReplay = new Replay();
manualClock = new ManualClock();
Child = new DependencyProvidingContainer
@ -67,7 +71,6 @@ namespace osu.Game.Tests.Visual.Gameplay
CachedDependencies = new[]
{
(typeof(SpectatorClient), (object)(spectatorClient = new TestSpectatorClient())),
(typeof(GameplayState), TestGameplayState.Create(new OsuRuleset()))
},
Children = new Drawable[]
{
@ -81,7 +84,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{
recordingManager = new TestRulesetInputManager(TestCustomisableModRuleset.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique)
{
Recorder = recorder = new TestReplayRecorder
Recorder = recorder = new TestReplayRecorder(recordingScore)
{
ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos),
},
@ -112,7 +115,7 @@ namespace osu.Game.Tests.Visual.Gameplay
playbackManager = new TestRulesetInputManager(TestCustomisableModRuleset.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique)
{
Clock = new FramedClock(manualClock),
ReplayInputHandler = replayHandler = new TestFramedReplayInputHandler(replay)
ReplayInputHandler = replayHandler = new TestFramedReplayInputHandler(playbackReplay)
{
GamefieldToScreenSpace = pos => playbackManager.ToScreenSpace(pos),
},
@ -144,6 +147,7 @@ namespace osu.Game.Tests.Visual.Gameplay
}
};
spectatorClient.BeginPlaying(TestGameplayState.Create(new OsuRuleset()), recordingScore);
spectatorClient.OnNewFrames += onNewFrames;
});
}
@ -151,15 +155,15 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestBasic()
{
AddUntilStep("received frames", () => replay.Frames.Count > 50);
AddUntilStep("received frames", () => playbackReplay.Frames.Count > 50);
AddStep("stop sending frames", () => recorder.Expire());
AddUntilStep("wait for all frames received", () => replay.Frames.Count == recorder.SentFrames.Count);
AddUntilStep("wait for all frames received", () => playbackReplay.Frames.Count == recorder.SentFrames.Count);
}
[Test]
public void TestWithSendFailure()
{
AddUntilStep("received frames", () => replay.Frames.Count > 50);
AddUntilStep("received frames", () => playbackReplay.Frames.Count > 50);
int framesReceivedSoFar = 0;
int frameSendAttemptsSoFar = 0;
@ -172,21 +176,21 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("wait for next send attempt", () =>
{
framesReceivedSoFar = replay.Frames.Count;
framesReceivedSoFar = playbackReplay.Frames.Count;
return spectatorClient.FrameSendAttempts > frameSendAttemptsSoFar + 1;
});
AddUntilStep("wait for more send attempts", () => spectatorClient.FrameSendAttempts > frameSendAttemptsSoFar + 10);
AddAssert("frames did not increase", () => framesReceivedSoFar == replay.Frames.Count);
AddAssert("frames did not increase", () => framesReceivedSoFar == playbackReplay.Frames.Count);
AddStep("stop failing sends", () => spectatorClient.ShouldFailSendingFrames = false);
AddUntilStep("wait for next frames", () => framesReceivedSoFar < replay.Frames.Count);
AddUntilStep("wait for next frames", () => framesReceivedSoFar < playbackReplay.Frames.Count);
AddStep("stop sending frames", () => recorder.Expire());
AddUntilStep("wait for all frames received", () => replay.Frames.Count == recorder.SentFrames.Count);
AddAssert("ensure frames were received in the correct sequence", () => replay.Frames.Select(f => f.Time).SequenceEqual(recorder.SentFrames.Select(f => f.Time)));
AddUntilStep("wait for all frames received", () => playbackReplay.Frames.Count == recorder.SentFrames.Count);
AddAssert("ensure frames were received in the correct sequence", () => playbackReplay.Frames.Select(f => f.Time).SequenceEqual(recorder.SentFrames.Select(f => f.Time)));
}
private void onNewFrames(int userId, FrameDataBundle frames)
@ -195,10 +199,10 @@ namespace osu.Game.Tests.Visual.Gameplay
{
var frame = new TestReplayFrame();
frame.FromLegacy(legacyFrame, null);
replay.Frames.Add(frame);
playbackReplay.Frames.Add(frame);
}
Logger.Log($"Received {frames.Frames.Count} new frames (total {replay.Frames.Count} of {recorder.SentFrames.Count})");
Logger.Log($"Received {frames.Frames.Count} new frames (total {playbackReplay.Frames.Count} of {recorder.SentFrames.Count})");
}
private double latency = SpectatorClient.TIME_BETWEEN_SENDS;
@ -219,7 +223,7 @@ namespace osu.Game.Tests.Visual.Gameplay
if (!replayHandler.HasFrames)
return;
var lastFrame = replay.Frames.LastOrDefault();
var lastFrame = playbackReplay.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.
@ -360,15 +364,8 @@ namespace osu.Game.Tests.Visual.Gameplay
{
public List<ReplayFrame> SentFrames = new List<ReplayFrame>();
public TestReplayRecorder()
: base(new Score
{
ScoreInfo =
{
BeatmapInfo = new BeatmapInfo(),
Ruleset = new OsuRuleset().RulesetInfo,
}
})
public TestReplayRecorder(Score score)
: base(score)
{
}

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.
#nullable disable
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
@ -24,8 +22,9 @@ namespace osu.Game.Tests.Visual.Gameplay
[TestFixture]
public class TestSceneStoryboard : OsuTestScene
{
private Container<DrawableStoryboard> storyboardContainer;
private DrawableStoryboard storyboard;
private Container<DrawableStoryboard> storyboardContainer = null!;
private DrawableStoryboard? storyboard;
[Test]
public void TestStoryboard()
@ -40,7 +39,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestStoryboardMissingVideo()
{
AddStep("Load storyboard with missing video", loadStoryboardNoVideo);
AddStep("Load storyboard with missing video", () => loadStoryboard("storyboard_no_video.osu"));
}
[BackgroundDependencyLoader]
@ -77,53 +76,44 @@ namespace osu.Game.Tests.Visual.Gameplay
Beatmap.BindValueChanged(beatmapChanged, true);
}
private void beatmapChanged(ValueChangedEvent<WorkingBeatmap> e) => loadStoryboard(e.NewValue);
private void beatmapChanged(ValueChangedEvent<WorkingBeatmap> e) => loadStoryboard(e.NewValue.Storyboard);
private void restart()
{
var track = Beatmap.Value.Track;
track.Reset();
loadStoryboard(Beatmap.Value);
loadStoryboard(Beatmap.Value.Storyboard);
track.Start();
}
private void loadStoryboard(IWorkingBeatmap working)
private void loadStoryboard(Storyboard toLoad)
{
if (storyboard != null)
storyboardContainer.Remove(storyboard);
storyboardContainer.Remove(storyboard, true);
var decoupledClock = new DecoupleableInterpolatingFramedClock { IsCoupled = true };
storyboardContainer.Clock = decoupledClock;
storyboard = working.Storyboard.CreateDrawable(SelectedMods.Value);
storyboard = toLoad.CreateDrawable(SelectedMods.Value);
storyboard.Passing = false;
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(SelectedMods.Value);
storyboardContainer.Add(storyboard);
decoupledClock.ChangeSource(Beatmap.Value.Track);
}
private void loadStoryboard(string filename)
{
Storyboard loaded;
using (var str = TestResources.OpenResource(filename))
using (var bfr = new LineBufferedReader(str))
{
var decoder = new LegacyStoryboardDecoder();
loaded = decoder.Decode(bfr);
}
loadStoryboard(loaded);
}
}
}

View File

@ -32,6 +32,7 @@ namespace osu.Game.Tests.Visual.Gameplay
protected new OutroPlayer Player => (OutroPlayer)base.Player;
private double currentBeatmapDuration;
private double currentStoryboardDuration;
private bool showResults = true;
@ -45,7 +46,8 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("enable storyboard", () => LocalConfig.SetValue(OsuSetting.ShowStoryboard, true));
AddStep("set dim level to 0", () => LocalConfig.SetValue<double>(OsuSetting.DimLevel, 0));
AddStep("reset fail conditions", () => currentFailConditions = (_, _) => false);
AddStep("set storyboard duration to 2s", () => currentStoryboardDuration = 2000);
AddStep("set beatmap duration to 0s", () => currentBeatmapDuration = 0);
AddStep("set storyboard duration to 8s", () => currentStoryboardDuration = 8000);
AddStep("set ShowResults = true", () => showResults = true);
}
@ -64,7 +66,7 @@ namespace osu.Game.Tests.Visual.Gameplay
public void TestStoryboardNoSkipOutro()
{
CreateTest();
AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.GameplayClock.CurrentTime >= currentStoryboardDuration);
AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.CurrentTime >= currentStoryboardDuration);
AddUntilStep("wait for score shown", () => Player.IsScoreShown);
}
@ -100,7 +102,7 @@ namespace osu.Game.Tests.Visual.Gameplay
});
AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed);
AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.GameplayClock.CurrentTime >= currentStoryboardDuration);
AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.CurrentTime >= currentStoryboardDuration);
AddUntilStep("wait for fail overlay", () => Player.FailOverlay.State.Value == Visibility.Visible);
}
@ -111,7 +113,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{
AddStep("set ShowResults = false", () => showResults = false);
});
AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.GameplayClock.CurrentTime >= currentStoryboardDuration);
AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.CurrentTime >= currentStoryboardDuration);
AddWaitStep("wait", 10);
AddAssert("no score shown", () => !Player.IsScoreShown);
}
@ -120,7 +122,7 @@ namespace osu.Game.Tests.Visual.Gameplay
public void TestStoryboardEndsBeforeCompletion()
{
CreateTest(() => AddStep("set storyboard duration to .1s", () => currentStoryboardDuration = 100));
AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.GameplayClock.CurrentTime >= currentStoryboardDuration);
AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.CurrentTime >= currentStoryboardDuration);
AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value);
AddUntilStep("wait for score shown", () => Player.IsScoreShown);
}
@ -138,7 +140,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("skip overlay content not visible", () => fadeContainer().State == Visibility.Hidden);
AddUntilStep("skip overlay content becomes visible", () => fadeContainer().State == Visibility.Visible);
AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.GameplayClock.CurrentTime >= currentStoryboardDuration);
AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.CurrentTime >= currentStoryboardDuration);
}
[Test]
@ -151,6 +153,24 @@ namespace osu.Game.Tests.Visual.Gameplay
AddAssert("player exited", () => Stack.CurrentScreen == null);
}
[Test]
public void TestPerformExitAfterOutro()
{
CreateTest(() =>
{
AddStep("set beatmap duration to 4s", () => currentBeatmapDuration = 4000);
AddStep("set storyboard duration to 1s", () => currentStoryboardDuration = 1000);
});
AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.CurrentTime >= currentStoryboardDuration);
AddStep("exit via pause", () => Player.ExitViaPause());
AddAssert("player paused", () => !Player.IsResuming);
AddStep("resume player", () => Player.Resume());
AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value);
AddUntilStep("wait for score shown", () => Player.IsScoreShown);
}
protected override bool AllowFail => true;
protected override Ruleset CreatePlayerRuleset() => new OsuRuleset();
@ -160,7 +180,7 @@ namespace osu.Game.Tests.Visual.Gameplay
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
{
var beatmap = new Beatmap();
beatmap.HitObjects.Add(new HitCircle());
beatmap.HitObjects.Add(new HitCircle { StartTime = currentBeatmapDuration });
return beatmap;
}
@ -189,7 +209,7 @@ namespace osu.Game.Tests.Visual.Gameplay
private event Func<HealthProcessor, JudgementResult, bool> failConditions;
public OutroPlayer(Func<HealthProcessor, JudgementResult, bool> failConditions, bool showResults = true)
: base(false, showResults)
: base(showResults: showResults)
{
this.failConditions = failConditions;
}

View File

@ -52,6 +52,7 @@ namespace osu.Game.Tests.Visual.Menus
},
notifications = new NotificationOverlay
{
Depth = float.MinValue,
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
}
@ -82,7 +83,14 @@ namespace osu.Game.Tests.Visual.Menus
[Test]
public virtual void TestPlayIntroWithFailingAudioDevice()
{
AddStep("hide notifications", () => notifications.Hide());
AddStep("reset notifications", () =>
{
notifications.Show();
notifications.Hide();
});
AddUntilStep("wait for no notifications", () => notifications.UnreadCount.Value, () => Is.EqualTo(0));
AddStep("restart sequence", () =>
{
logo.FinishTransforms();

View File

@ -62,8 +62,8 @@ namespace osu.Game.Tests.Visual.Menus
[SetUp]
public void SetUp() => Schedule(() =>
{
Remove(nowPlayingOverlay);
Remove(volumeOverlay);
Remove(nowPlayingOverlay, false);
Remove(volumeOverlay, false);
Children = new Drawable[]
{

View File

@ -0,0 +1,87 @@
// 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.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Online.API;
using osu.Game.Overlays.Toolbar;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Tests.Visual.Menus
{
[TestFixture]
public class TestSceneToolbarUserButton : OsuManualInputManagerTestScene
{
public TestSceneToolbarUserButton()
{
Container mainContainer;
Children = new Drawable[]
{
mainContainer = new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.X,
Height = Toolbar.HEIGHT,
Children = new Drawable[]
{
new Box
{
Colour = Color4.Black,
RelativeSizeAxes = Axes.Both,
},
new FillFlowContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Y,
AutoSizeAxes = Axes.X,
Direction = FillDirection.Horizontal,
Children = new Drawable[]
{
new Box
{
Colour = Color4.DarkRed,
RelativeSizeAxes = Axes.Y,
Width = 2,
},
new ToolbarUserButton(),
new Box
{
Colour = Color4.DarkRed,
RelativeSizeAxes = Axes.Y,
Width = 2,
},
}
},
}
},
};
AddSliderStep("scale", 0.5, 4, 1, scale => mainContainer.Scale = new Vector2((float)scale));
}
[Test]
public void TestLoginLogout()
{
AddStep("Log out", () => ((DummyAPIAccess)API).Logout());
AddStep("Log in", () => ((DummyAPIAccess)API).Login("wang", "jang"));
}
[Test]
public void TestStates()
{
AddStep("Log in", () => ((DummyAPIAccess)API).Login("wang", "jang"));
foreach (var state in Enum.GetValues<APIState>())
{
AddStep($"Change state to {state}", () => ((DummyAPIAccess)API).SetState(state));
}
}
}
}

View File

@ -23,7 +23,7 @@ namespace osu.Game.Tests.Visual.Mods
protected override TestPlayer CreateModPlayer(Ruleset ruleset)
{
var player = base.CreateModPlayer(ruleset);
player.RestartRequested = () => restartRequested = true;
player.RestartRequested = _ => restartRequested = true;
return player;
}

View File

@ -35,7 +35,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
protected IScreen CurrentSubScreen => multiplayerComponents.MultiplayerScreen.CurrentSubScreen;
private BeatmapManager beatmaps;
private RulesetStore rulesets;
private BeatmapSetInfo importedSet;
private TestMultiplayerComponents multiplayerComponents;
@ -45,8 +44,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
[BackgroundDependencyLoader]
private void load(GameHost host, AudioManager audio)
{
Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default));
Dependencies.Cache(new RealmRulesetStore(Realm));
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default));
Dependencies.Cache(Realm);
}

View File

@ -19,29 +19,33 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
private DrawableRoomParticipantsList list;
[SetUp]
public new void Setup() => Schedule(() =>
public override void SetUpSteps()
{
SelectedRoom.Value = new Room
{
Name = { Value = "test room" },
Host =
{
Value = new APIUser
{
Id = 2,
Username = "peppy",
}
}
};
base.SetUpSteps();
Child = list = new DrawableRoomParticipantsList
AddStep("create list", () =>
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
NumberOfCircles = 4
};
});
SelectedRoom.Value = new Room
{
Name = { Value = "test room" },
Host =
{
Value = new APIUser
{
Id = 2,
Username = "peppy",
}
}
};
Child = list = new DrawableRoomParticipantsList
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
NumberOfCircles = 4
};
});
}
[Test]
public void TestCircleCountNearLimit()

View File

@ -18,6 +18,7 @@ using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Database;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Cursor;
using osu.Game.Models;
using osu.Game.Online.API;
using osu.Game.Online.Rooms;
@ -37,13 +38,12 @@ namespace osu.Game.Tests.Visual.Multiplayer
private TestPlaylist playlist;
private BeatmapManager manager;
private RulesetStore rulesets;
[BackgroundDependencyLoader]
private void load(GameHost host, AudioManager audio)
{
Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default));
Dependencies.Cache(new RealmRulesetStore(Realm));
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default));
Dependencies.Cache(Realm);
}
@ -195,12 +195,15 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Test]
public void TestDownloadButtonHiddenWhenBeatmapExists()
{
var beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo;
Live<BeatmapSetInfo> imported = null;
Debug.Assert(beatmap.BeatmapSet != null);
AddStep("import beatmap", () =>
{
var beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo;
AddStep("import beatmap", () => imported = manager.Import(beatmap.BeatmapSet));
Debug.Assert(beatmap.BeatmapSet != null);
imported = manager.Import(beatmap.BeatmapSet);
});
createPlaylistWithBeatmaps(() => imported.PerformRead(s => s.Beatmaps.Detach()));
@ -245,40 +248,35 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Test]
public void TestExpiredItems()
{
AddStep("create playlist", () =>
createPlaylist(p =>
{
Child = playlist = new TestPlaylist
p.Items.Clear();
p.Items.AddRange(new[]
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(500, 300),
Items =
new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo)
{
new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo)
ID = 0,
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
Expired = true,
RequiredMods = new[]
{
ID = 0,
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
Expired = true,
RequiredMods = new[]
{
new APIMod(new OsuModHardRock()),
new APIMod(new OsuModDoubleTime()),
new APIMod(new OsuModAutoplay())
}
},
new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo)
new APIMod(new OsuModHardRock()),
new APIMod(new OsuModDoubleTime()),
new APIMod(new OsuModAutoplay())
}
},
new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo)
{
ID = 1,
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
RequiredMods = new[]
{
ID = 1,
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
RequiredMods = new[]
{
new APIMod(new OsuModHardRock()),
new APIMod(new OsuModDoubleTime()),
new APIMod(new OsuModAutoplay())
}
new APIMod(new OsuModHardRock()),
new APIMod(new OsuModDoubleTime()),
new APIMod(new OsuModAutoplay())
}
}
};
});
});
AddUntilStep("wait for items to load", () => playlist.ItemMap.Values.All(i => i.IsLoaded));
@ -321,19 +319,44 @@ namespace osu.Game.Tests.Visual.Multiplayer
=> AddAssert($"delete button {index} {(visible ? "is" : "is not")} visible",
() => (playlist.ChildrenOfType<DrawableRoomPlaylistItem.PlaylistRemoveButton>().ElementAt(2 + index * 2).Alpha > 0) == visible);
private void createPlaylistWithBeatmaps(Func<IEnumerable<IBeatmapInfo>> beatmaps) => createPlaylist(p =>
{
int index = 0;
p.Items.Clear();
foreach (var b in beatmaps())
{
p.Items.Add(new PlaylistItem(b)
{
ID = index++,
OwnerID = 2,
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
RequiredMods = new[]
{
new APIMod(new OsuModHardRock()),
new APIMod(new OsuModDoubleTime()),
new APIMod(new OsuModAutoplay())
}
});
}
});
private void createPlaylist(Action<TestPlaylist> setupPlaylist = null)
{
AddStep("create playlist", () =>
{
Child = playlist = new TestPlaylist
Child = new OsuContextMenuContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(500, 300)
RelativeSizeAxes = Axes.Both,
Child = playlist = new TestPlaylist
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(500, 300)
}
};
setupPlaylist?.Invoke(playlist);
for (int i = 0; i < 20; i++)
{
playlist.Items.Add(new PlaylistItem(i % 2 == 1
@ -360,39 +383,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
}
});
}
});
AddUntilStep("wait for items to load", () => playlist.ItemMap.Values.All(i => i.IsLoaded));
}
private void createPlaylistWithBeatmaps(Func<IEnumerable<IBeatmapInfo>> beatmaps)
{
AddStep("create playlist", () =>
{
Child = playlist = new TestPlaylist
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(500, 300)
};
int index = 0;
foreach (var b in beatmaps())
{
playlist.Items.Add(new PlaylistItem(b)
{
ID = index++,
OwnerID = 2,
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
RequiredMods = new[]
{
new APIMod(new OsuModHardRock()),
new APIMod(new OsuModDoubleTime()),
new APIMod(new OsuModAutoplay())
}
});
}
setupPlaylist?.Invoke(playlist);
});
AddUntilStep("wait for items to load", () => playlist.ItemMap.Values.All(i => i.IsLoaded));

View File

@ -25,23 +25,27 @@ namespace osu.Game.Tests.Visual.Multiplayer
private RoomsContainer container;
[SetUp]
public new void Setup() => Schedule(() =>
public override void SetUpSteps()
{
Child = new PopoverContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Width = 0.5f,
base.SetUpSteps();
Child = container = new RoomsContainer
AddStep("create container", () =>
{
Child = new PopoverContainer
{
SelectedRoom = { BindTarget = SelectedRoom }
}
};
});
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Width = 0.5f,
Child = container = new RoomsContainer
{
SelectedRoom = { BindTarget = SelectedRoom }
}
};
});
}
[Test]
public void TestBasicListChanges()

View File

@ -3,7 +3,6 @@
#nullable disable
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Game.Online.API;
using osu.Game.Online.Rooms;
@ -18,19 +17,23 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneMatchBeatmapDetailArea : OnlinePlayTestScene
{
[SetUp]
public new void Setup() => Schedule(() =>
public override void SetUpSteps()
{
SelectedRoom.Value = new Room();
base.SetUpSteps();
Child = new MatchBeatmapDetailArea
AddStep("create area", () =>
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(500),
CreateNewItem = createNewItem
};
});
SelectedRoom.Value = new Room();
Child = new MatchBeatmapDetailArea
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(500),
CreateNewItem = createNewItem
};
});
}
private void createNewItem()
{

View File

@ -4,8 +4,6 @@
#nullable disable
using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses;
@ -19,59 +17,62 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneMatchLeaderboard : OnlinePlayTestScene
{
[BackgroundDependencyLoader]
private void load()
public override void SetUpSteps()
{
((DummyAPIAccess)API).HandleRequest = r =>
base.SetUpSteps();
AddStep("setup API", () =>
{
switch (r)
((DummyAPIAccess)API).HandleRequest = r =>
{
case GetRoomLeaderboardRequest leaderboardRequest:
leaderboardRequest.TriggerSuccess(new APILeaderboard
{
Leaderboard = new List<APIUserScoreAggregate>
switch (r)
{
case GetRoomLeaderboardRequest leaderboardRequest:
leaderboardRequest.TriggerSuccess(new APILeaderboard
{
new APIUserScoreAggregate
Leaderboard = new List<APIUserScoreAggregate>
{
UserID = 2,
User = new APIUser { Id = 2, Username = "peppy" },
TotalScore = 995533,
RoomID = 3,
CompletedBeatmaps = 1,
TotalAttempts = 6,
Accuracy = 0.9851
},
new APIUserScoreAggregate
{
UserID = 1040328,
User = new APIUser { Id = 1040328, Username = "smoogipoo" },
TotalScore = 981100,
RoomID = 3,
CompletedBeatmaps = 1,
TotalAttempts = 9,
Accuracy = 0.937
new APIUserScoreAggregate
{
UserID = 2,
User = new APIUser { Id = 2, Username = "peppy" },
TotalScore = 995533,
RoomID = 3,
CompletedBeatmaps = 1,
TotalAttempts = 6,
Accuracy = 0.9851
},
new APIUserScoreAggregate
{
UserID = 1040328,
User = new APIUser { Id = 1040328, Username = "smoogipoo" },
TotalScore = 981100,
RoomID = 3,
CompletedBeatmaps = 1,
TotalAttempts = 9,
Accuracy = 0.937
}
}
}
});
return true;
}
});
return true;
}
return false;
};
}
return false;
};
});
[SetUp]
public new void Setup() => Schedule(() =>
{
SelectedRoom.Value = new Room { RoomID = { Value = 3 } };
Child = new MatchLeaderboard
AddStep("create leaderboard", () =>
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Size = new Vector2(550f, 450f),
Scope = MatchLeaderboardScope.Overall,
};
});
SelectedRoom.Value = new Room { RoomID = { Value = 3 } };
Child = new MatchLeaderboard
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Size = new Vector2(550f, 450f),
Scope = MatchLeaderboardScope.Overall,
};
});
}
}
}

View File

@ -91,8 +91,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
break;
case StopCountdownRequest:
multiplayerRoom.Countdown = null;
raiseRoomUpdated();
clearRoomCountdown();
break;
}
});
@ -244,14 +243,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
});
AddStep("start countdown", () => multiplayerClient.Object.SendMatchRequest(new StartMatchCountdownRequest { Duration = TimeSpan.FromMinutes(1) }).WaitSafely());
AddUntilStep("countdown started", () => multiplayerRoom.Countdown != null);
AddUntilStep("countdown started", () => multiplayerRoom.ActiveCountdowns.Any());
AddStep("transfer host to local user", () => transferHost(localUser));
AddUntilStep("local user is host", () => multiplayerRoom.Host?.Equals(multiplayerClient.Object.LocalUser) == true);
ClickButtonWhenEnabled<MultiplayerReadyButton>();
checkLocalUserState(MultiplayerUserState.Ready);
AddAssert("countdown still active", () => multiplayerRoom.Countdown != null);
AddAssert("countdown still active", () => multiplayerRoom.ActiveCountdowns.Any());
}
[Test]
@ -392,7 +391,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
private void setRoomCountdown(TimeSpan duration)
{
multiplayerRoom.Countdown = new MatchStartCountdown { TimeRemaining = duration };
multiplayerRoom.ActiveCountdowns.Add(new MatchStartCountdown { TimeRemaining = duration });
raiseRoomUpdated();
}
private void clearRoomCountdown()
{
multiplayerRoom.ActiveCountdowns.Clear();
raiseRoomUpdated();
}

View File

@ -22,8 +22,10 @@ namespace osu.Game.Tests.Visual.Multiplayer
private MultiSpectatorLeaderboard leaderboard;
[SetUpSteps]
public new void SetUpSteps()
public override void SetUpSteps()
{
base.SetUpSteps();
AddStep("reset", () =>
{
leaderboard?.RemoveAndDisposeImmediately();
@ -118,6 +120,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
=> AddStep($"set user {userId} time {time}", () => clocks[userId].CurrentTime = time);
private void assertCombo(int userId, int expectedCombo)
=> AddUntilStep($"player {userId} has {expectedCombo} combo", () => this.ChildrenOfType<GameplayLeaderboardScore>().Single(s => s.User?.Id == userId).Combo.Value == expectedCombo);
=> AddUntilStep($"player {userId} has {expectedCombo} combo", () => this.ChildrenOfType<GameplayLeaderboardScore>().Single(s => s.User?.OnlineID == userId).Combo.Value == expectedCombo);
}
}

View File

@ -13,9 +13,11 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate;
using osu.Game.Screens.Play;
@ -56,8 +58,12 @@ namespace osu.Game.Tests.Visual.Multiplayer
importedBeatmapId = importedBeatmap.OnlineID;
}
[SetUp]
public new void Setup() => Schedule(() => playingUsers.Clear());
public override void SetUpSteps()
{
base.SetUpSteps();
AddStep("clear playing users", () => playingUsers.Clear());
}
[Test]
public void TestDelayedStart()
@ -161,11 +167,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
sendFrames(PLAYER_1_ID, 40);
sendFrames(PLAYER_2_ID, 20);
checkPaused(PLAYER_2_ID, true);
checkPausedInstant(PLAYER_1_ID, false);
waitUntilPaused(PLAYER_2_ID);
checkRunningInstant(PLAYER_1_ID);
AddAssert("master clock still running", () => this.ChildrenOfType<MasterGameplayClockContainer>().Single().IsRunning);
checkPaused(PLAYER_1_ID, true);
waitUntilPaused(PLAYER_1_ID);
AddUntilStep("master clock paused", () => !this.ChildrenOfType<MasterGameplayClockContainer>().Single().IsRunning);
}
@ -177,13 +183,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
// Send frames for one player only, both should remain paused.
sendFrames(PLAYER_1_ID, 20);
checkPausedInstant(PLAYER_1_ID, true);
checkPausedInstant(PLAYER_2_ID, true);
checkPausedInstant(PLAYER_1_ID);
checkPausedInstant(PLAYER_2_ID);
// Send frames for the other player, both should now start playing.
sendFrames(PLAYER_2_ID, 20);
checkPausedInstant(PLAYER_1_ID, false);
checkPausedInstant(PLAYER_2_ID, false);
checkRunningInstant(PLAYER_1_ID);
checkRunningInstant(PLAYER_2_ID);
}
[Test]
@ -194,15 +200,15 @@ namespace osu.Game.Tests.Visual.Multiplayer
// Send frames for one player only, both should remain paused.
sendFrames(PLAYER_1_ID, 1000);
checkPausedInstant(PLAYER_1_ID, true);
checkPausedInstant(PLAYER_2_ID, true);
checkPausedInstant(PLAYER_1_ID);
checkPausedInstant(PLAYER_2_ID);
// Wait for the start delay seconds...
AddWaitStep("wait maximum start delay seconds", (int)(CatchUpSyncManager.MAXIMUM_START_DELAY / TimePerAction));
AddWaitStep("wait maximum start delay seconds", (int)(SpectatorSyncManager.MAXIMUM_START_DELAY / TimePerAction));
// Player 1 should start playing by itself, player 2 should remain paused.
checkPausedInstant(PLAYER_1_ID, false);
checkPausedInstant(PLAYER_2_ID, true);
checkRunningInstant(PLAYER_1_ID);
checkPausedInstant(PLAYER_2_ID);
}
[Test]
@ -214,26 +220,26 @@ namespace osu.Game.Tests.Visual.Multiplayer
// Send initial frames for both players. A few more for player 1.
sendFrames(PLAYER_1_ID, 20);
sendFrames(PLAYER_2_ID);
checkPausedInstant(PLAYER_1_ID, false);
checkPausedInstant(PLAYER_2_ID, false);
checkRunningInstant(PLAYER_1_ID);
checkRunningInstant(PLAYER_2_ID);
// Eventually player 2 will pause, player 1 must remain running.
checkPaused(PLAYER_2_ID, true);
checkPausedInstant(PLAYER_1_ID, false);
waitUntilPaused(PLAYER_2_ID);
checkRunningInstant(PLAYER_1_ID);
// Eventually both players will run out of frames and should pause.
checkPaused(PLAYER_1_ID, true);
checkPausedInstant(PLAYER_2_ID, true);
waitUntilPaused(PLAYER_1_ID);
checkPausedInstant(PLAYER_2_ID);
// Send more frames for the first player only. Player 1 should start playing with player 2 remaining paused.
sendFrames(PLAYER_1_ID, 20);
checkPausedInstant(PLAYER_2_ID, true);
checkPausedInstant(PLAYER_1_ID, false);
checkPausedInstant(PLAYER_2_ID);
checkRunningInstant(PLAYER_1_ID);
// Send more frames for the second player. Both should be playing
sendFrames(PLAYER_2_ID, 20);
checkPausedInstant(PLAYER_2_ID, false);
checkPausedInstant(PLAYER_1_ID, false);
checkRunningInstant(PLAYER_2_ID);
checkRunningInstant(PLAYER_1_ID);
}
[Test]
@ -245,16 +251,16 @@ namespace osu.Game.Tests.Visual.Multiplayer
// Send initial frames for both players. A few more for player 1.
sendFrames(PLAYER_1_ID, 1000);
sendFrames(PLAYER_2_ID, 30);
checkPausedInstant(PLAYER_1_ID, false);
checkPausedInstant(PLAYER_2_ID, false);
checkRunningInstant(PLAYER_1_ID);
checkRunningInstant(PLAYER_2_ID);
// Eventually player 2 will run out of frames and should pause.
checkPaused(PLAYER_2_ID, true);
waitUntilPaused(PLAYER_2_ID);
AddWaitStep("wait a few more frames", 10);
// Send more frames for player 2. It should unpause.
sendFrames(PLAYER_2_ID, 1000);
checkPausedInstant(PLAYER_2_ID, false);
checkRunningInstant(PLAYER_2_ID);
// Player 2 should catch up to player 1 after unpausing.
waitForCatchup(PLAYER_2_ID);
@ -267,21 +273,28 @@ namespace osu.Game.Tests.Visual.Multiplayer
start(new[] { PLAYER_1_ID, PLAYER_2_ID });
loadSpectateScreen();
// With no frames, the synchronisation state will be TooFarAhead.
// In this state, all players should be muted.
assertMuted(PLAYER_1_ID, true);
assertMuted(PLAYER_2_ID, true);
sendFrames(PLAYER_1_ID);
// Send frames for both players, with more frames for player 2.
sendFrames(PLAYER_1_ID, 5);
sendFrames(PLAYER_2_ID, 20);
checkPaused(PLAYER_1_ID, false);
assertOneNotMuted();
checkPaused(PLAYER_1_ID, true);
// While both players are running, one of them should be un-muted.
waitUntilRunning(PLAYER_1_ID);
assertOnePlayerNotMuted();
// After player 1 runs out of frames, the un-muted player should always be player 2.
waitUntilPaused(PLAYER_1_ID);
waitUntilRunning(PLAYER_2_ID);
assertMuted(PLAYER_1_ID, true);
assertMuted(PLAYER_2_ID, false);
sendFrames(PLAYER_1_ID, 100);
waitForCatchup(PLAYER_1_ID);
checkPaused(PLAYER_2_ID, true);
waitUntilPaused(PLAYER_2_ID);
assertMuted(PLAYER_1_ID, false);
assertMuted(PLAYER_2_ID, true);
@ -314,13 +327,25 @@ namespace osu.Game.Tests.Visual.Multiplayer
loadSpectateScreen();
sendFrames(PLAYER_1_ID, 300);
AddWaitStep("wait maximum start delay seconds", (int)(CatchUpSyncManager.MAXIMUM_START_DELAY / TimePerAction));
checkPaused(PLAYER_1_ID, false);
AddWaitStep("wait maximum start delay seconds", (int)(SpectatorSyncManager.MAXIMUM_START_DELAY / TimePerAction));
waitUntilRunning(PLAYER_1_ID);
sendFrames(PLAYER_2_ID, 300);
AddUntilStep("player 2 playing from correct point in time", () => getPlayer(PLAYER_2_ID).ChildrenOfType<DrawableRuleset>().Single().FrameStableClock.CurrentTime > 30000);
}
[Test]
public void TestGameplayRateAdjust()
{
start(getPlayerIds(4), mods: new[] { new APIMod(new OsuModDoubleTime()) });
loadSpectateScreen();
sendFrames(getPlayerIds(4), 300);
AddUntilStep("wait for correct track speed", () => Beatmap.Value.Track.Rate, () => Is.EqualTo(1.5));
}
[Test]
public void TestPlayersLeaveWhileSpectating()
{
@ -353,12 +378,18 @@ namespace osu.Game.Tests.Visual.Multiplayer
/// <summary>
/// Tests spectating with a beatmap that has a high <see cref="BeatmapInfo.AudioLeadIn"/> value.
///
/// This test is not intended not to check the correct initial time value, but only to guard against
/// gameplay potentially getting stuck in a stopped state due to lead in time being present.
/// </summary>
[Test]
public void TestAudioLeadIn() => testLeadIn(b => b.BeatmapInfo.AudioLeadIn = 2000);
/// <summary>
/// Tests spectating with a beatmap that has a storyboard element with a negative start time (i.e. intro storyboard element).
///
/// This test is not intended not to check the correct initial time value, but only to guard against
/// gameplay potentially getting stuck in a stopped state due to lead in time being present.
/// </summary>
[Test]
public void TestIntroStoryboardElement() => testLeadIn(b =>
@ -380,10 +411,10 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddUntilStep("wait for player load", () => spectatorScreen.AllPlayersLoaded);
AddWaitStep("wait for progression", 3);
AddUntilStep("wait for clock running", () => getInstance(PLAYER_1_ID).SpectatorPlayerClock.IsRunning);
assertNotCatchingUp(PLAYER_1_ID);
assertRunning(PLAYER_1_ID);
waitUntilRunning(PLAYER_1_ID);
}
private void loadSpectateScreen(bool waitForPlayerLoad = true, Action<WorkingBeatmap>? applyToBeatmap = null)
@ -403,7 +434,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
private void start(int userId, int? beatmapId = null) => start(new[] { userId }, beatmapId);
private void start(int[] userIds, int? beatmapId = null)
private void start(int[] userIds, int? beatmapId = null, APIMod[]? mods = null)
{
AddStep("start play", () =>
{
@ -412,10 +443,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
var user = new MultiplayerRoomUser(id)
{
User = new APIUser { Id = id },
Mods = mods ?? Array.Empty<APIMod>(),
};
OnlinePlayDependencies.MultiplayerClient.AddUser(user.User, true);
SpectatorClient.SendStartPlay(id, beatmapId ?? importedBeatmapId);
OnlinePlayDependencies.MultiplayerClient.AddUser(user, true);
SpectatorClient.SendStartPlay(id, beatmapId ?? importedBeatmapId, mods);
playingUsers.Add(user);
}
@ -428,13 +460,17 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
var user = playingUsers.Single(u => u.UserID == userId);
OnlinePlayDependencies.MultiplayerClient.RemoveUser(user.User.AsNonNull());
SpectatorClient.SendEndPlay(userId);
OnlinePlayDependencies.MultiplayerClient.RemoveUser(user.User.AsNonNull());
playingUsers.Remove(user);
});
}
/// <summary>
/// Send new frames on behalf of a user.
/// Frames will last for count * 100 milliseconds.
/// </summary>
private void sendFrames(int userId, int count = 10) => sendFrames(new[] { userId }, count);
private void sendFrames(int[] userIds, int count = 10)
@ -446,36 +482,47 @@ namespace osu.Game.Tests.Visual.Multiplayer
});
}
private void checkPaused(int userId, bool state)
=> AddUntilStep($"{userId} is {(state ? "paused" : "playing")}", () => getPlayer(userId).ChildrenOfType<GameplayClockContainer>().First().GameplayClock.IsRunning != state);
private void checkPausedInstant(int userId, bool state)
private void checkRunningInstant(int userId)
{
checkPaused(userId, state);
waitUntilRunning(userId);
// Todo: The following should work, but is broken because SpectatorScreen retrieves the WorkingBeatmap via the BeatmapManager, bypassing the test scene clock and running real-time.
// AddAssert($"{userId} is {(state ? "paused" : "playing")}", () => getPlayer(userId).ChildrenOfType<GameplayClockContainer>().First().GameplayClock.IsRunning != state);
}
private void assertOneNotMuted() => AddAssert("one player not muted", () => spectatorScreen.ChildrenOfType<PlayerArea>().Count(p => !p.Mute) == 1);
private void checkPausedInstant(int userId)
{
waitUntilPaused(userId);
// Todo: The following should work, but is broken because SpectatorScreen retrieves the WorkingBeatmap via the BeatmapManager, bypassing the test scene clock and running real-time.
// AddAssert($"{userId} is {(state ? "paused" : "playing")}", () => getPlayer(userId).ChildrenOfType<GameplayClockContainer>().First().GameplayClock.IsRunning != state);
}
private void assertOnePlayerNotMuted() => AddAssert(nameof(assertOnePlayerNotMuted), () => spectatorScreen.ChildrenOfType<PlayerArea>().Count(p => !p.Mute) == 1);
private void assertMuted(int userId, bool muted)
=> AddAssert($"{userId} {(muted ? "is" : "is not")} muted", () => getInstance(userId).Mute == muted);
=> AddAssert($"{nameof(assertMuted)}({userId}, {muted})", () => getInstance(userId).Mute == muted);
private void assertRunning(int userId)
=> AddAssert($"{userId} clock running", () => getInstance(userId).GameplayClock.IsRunning);
=> AddAssert($"{nameof(assertRunning)}({userId})", () => getInstance(userId).SpectatorPlayerClock.IsRunning);
private void waitUntilPaused(int userId)
=> AddUntilStep($"{nameof(waitUntilPaused)}({userId})", () => !getPlayer(userId).ChildrenOfType<GameplayClockContainer>().First().IsRunning);
private void waitUntilRunning(int userId)
=> AddUntilStep($"{nameof(waitUntilRunning)}({userId})", () => getPlayer(userId).ChildrenOfType<GameplayClockContainer>().First().IsRunning);
private void assertNotCatchingUp(int userId)
=> AddAssert($"{userId} in sync", () => !getInstance(userId).GameplayClock.IsCatchingUp);
=> AddAssert($"{nameof(assertNotCatchingUp)}({userId})", () => !getInstance(userId).SpectatorPlayerClock.IsCatchingUp);
private void waitForCatchup(int userId)
=> AddUntilStep($"{userId} not catching up", () => !getInstance(userId).GameplayClock.IsCatchingUp);
=> AddUntilStep($"{nameof(waitForCatchup)}({userId})", () => !getInstance(userId).SpectatorPlayerClock.IsCatchingUp);
private Player getPlayer(int userId) => getInstance(userId).ChildrenOfType<Player>().Single();
private PlayerArea getInstance(int userId) => spectatorScreen.ChildrenOfType<PlayerArea>().Single(p => p.UserId == userId);
private GameplayLeaderboardScore getLeaderboardScore(int userId) => spectatorScreen.ChildrenOfType<GameplayLeaderboardScore>().Single(s => s.User?.Id == userId);
private GameplayLeaderboardScore getLeaderboardScore(int userId) => spectatorScreen.ChildrenOfType<GameplayLeaderboardScore>().Single(s => s.User?.OnlineID == userId);
private int[] getPlayerIds(int count) => Enumerable.Range(PLAYER_1_ID, count).ToArray();
}

View File

@ -24,7 +24,6 @@ using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Rooms;
using osu.Game.Overlays.Mods;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Catch;
using osu.Game.Rulesets.Mods;
@ -50,7 +49,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
public class TestSceneMultiplayer : ScreenTestScene
{
private BeatmapManager beatmaps = null!;
private RulesetStore rulesets = null!;
private BeatmapSetInfo importedSet = null!;
private TestMultiplayerComponents multiplayerComponents = null!;
@ -64,8 +62,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
[BackgroundDependencyLoader]
private void load(GameHost host, AudioManager audio)
{
Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, API, audio, Resources, host, Beatmap.Default));
Dependencies.Cache(new RealmRulesetStore(Realm));
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, API, audio, Resources, host, Beatmap.Default));
Dependencies.Cache(Realm);
}
@ -402,16 +400,18 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Test]
public void TestPlayStartsWithCorrectBeatmapWhileAtSongSelect()
{
createRoom(() => new Room
PlaylistItem? item = null;
createRoom(() =>
{
Name = { Value = "Test Room" },
Playlist =
item = new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
}
}
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
};
return new Room
{
Name = { Value = "Test Room" },
Playlist = { item }
};
});
pressReadyButton();
@ -419,7 +419,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("Enter song select", () =>
{
var currentSubScreen = ((Screens.OnlinePlay.Multiplayer.Multiplayer)multiplayerComponents.CurrentScreen).CurrentSubScreen;
((MultiplayerMatchSubScreen)currentSubScreen).OpenSongSelection(multiplayerClient.ClientRoom?.Settings.PlaylistItemId);
((MultiplayerMatchSubScreen)currentSubScreen).OpenSongSelection(item);
});
AddUntilStep("wait for song select", () => this.ChildrenOfType<MultiplayerMatchSongSelect>().FirstOrDefault()?.BeatmapSetsLoaded == true);
@ -440,16 +440,18 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Test]
public void TestPlayStartsWithCorrectRulesetWhileAtSongSelect()
{
createRoom(() => new Room
PlaylistItem? item = null;
createRoom(() =>
{
Name = { Value = "Test Room" },
Playlist =
item = new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
}
}
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
};
return new Room
{
Name = { Value = "Test Room" },
Playlist = { item }
};
});
pressReadyButton();
@ -457,7 +459,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("Enter song select", () =>
{
var currentSubScreen = ((Screens.OnlinePlay.Multiplayer.Multiplayer)multiplayerComponents.CurrentScreen).CurrentSubScreen;
((MultiplayerMatchSubScreen)currentSubScreen).OpenSongSelection(multiplayerClient.ClientRoom?.Settings.PlaylistItemId);
((MultiplayerMatchSubScreen)currentSubScreen).OpenSongSelection(item);
});
AddUntilStep("wait for song select", () => this.ChildrenOfType<MultiplayerMatchSongSelect>().FirstOrDefault()?.BeatmapSetsLoaded == true);
@ -478,16 +480,18 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Test]
public void TestPlayStartsWithCorrectModsWhileAtSongSelect()
{
createRoom(() => new Room
PlaylistItem? item = null;
createRoom(() =>
{
Name = { Value = "Test Room" },
Playlist =
item = new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
}
}
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
};
return new Room
{
Name = { Value = "Test Room" },
Playlist = { item }
};
});
pressReadyButton();
@ -495,7 +499,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("Enter song select", () =>
{
var currentSubScreen = ((Screens.OnlinePlay.Multiplayer.Multiplayer)multiplayerComponents.CurrentScreen).CurrentSubScreen;
((MultiplayerMatchSubScreen)currentSubScreen).OpenSongSelection(multiplayerClient.ClientRoom?.Settings.PlaylistItemId);
((MultiplayerMatchSubScreen)currentSubScreen).OpenSongSelection(item);
});
AddUntilStep("wait for song select", () => this.ChildrenOfType<MultiplayerMatchSongSelect>().FirstOrDefault()?.BeatmapSetsLoaded == true);
@ -627,7 +631,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("invoke on back button", () => multiplayerComponents.OnBackButton());
AddAssert("mod overlay is hidden", () => this.ChildrenOfType<UserModSelectOverlay>().Single().State.Value == Visibility.Hidden);
AddAssert("mod overlay is hidden", () => this.ChildrenOfType<RoomSubScreen>().Single().UserModsSelectOverlay.State.Value == Visibility.Hidden);
AddAssert("dialog overlay is hidden", () => DialogOverlay.State.Value == Visibility.Hidden);
@ -667,7 +671,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
for (double i = 1000; i < TestResources.QUICK_BEATMAP_LENGTH; i += 1000)
{
double time = i;
AddUntilStep($"wait for time > {i}", () => this.ChildrenOfType<GameplayClockContainer>().SingleOrDefault()?.GameplayClock.CurrentTime > time);
AddUntilStep($"wait for time > {i}", () => this.ChildrenOfType<GameplayClockContainer>().SingleOrDefault()?.CurrentTime > time);
}
AddUntilStep("wait for results", () => multiplayerComponents.CurrentScreen is ResultsScreen);

View File

@ -3,7 +3,6 @@
#nullable disable
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
@ -13,23 +12,27 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneMultiplayerMatchFooter : MultiplayerTestScene
{
[SetUp]
public new void Setup() => Schedule(() =>
public override void SetUpSteps()
{
Child = new PopoverContainer
base.SetUpSteps();
AddStep("create footer", () =>
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Child = new Container
Child = new PopoverContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.X,
Height = 50,
Child = new MultiplayerMatchFooter()
}
};
});
RelativeSizeAxes = Axes.Both,
Child = new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.X,
Height = 50,
Child = new MultiplayerMatchFooter()
}
};
});
}
}
}

View File

@ -19,7 +19,6 @@ using osu.Game.Database;
using osu.Game.Online.Rooms;
using osu.Game.Overlays.Mods;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Catch;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods;
@ -47,7 +46,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
private void load(GameHost host, AudioManager audio)
{
Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default));
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default));
Dependencies.Cache(Realm);
importedBeatmapSet = manager.Import(TestResources.CreateTestBeatmapSetInfo(8, rulesets.AvailableRulesets.ToArray()));
@ -68,37 +67,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddUntilStep("wait for present", () => songSelect.IsCurrentScreen() && songSelect.BeatmapSetsLoaded);
}
[Test]
public void TestBeatmapRevertedOnExitIfNoSelection()
{
BeatmapInfo selectedBeatmap = null;
AddStep("select beatmap",
() => songSelect.Carousel.SelectBeatmap(selectedBeatmap = beatmaps.Where(beatmap => beatmap.Ruleset.OnlineID == new OsuRuleset().LegacyID).ElementAt(1)));
AddUntilStep("wait for selection", () => Beatmap.Value.BeatmapInfo.Equals(selectedBeatmap));
AddStep("exit song select", () => songSelect.Exit());
AddAssert("beatmap reverted", () => Beatmap.IsDefault);
}
[Test]
public void TestModsRevertedOnExitIfNoSelection()
{
AddStep("change mods", () => SelectedMods.Value = new[] { new OsuModDoubleTime() });
AddStep("exit song select", () => songSelect.Exit());
AddAssert("mods reverted", () => SelectedMods.Value.Count == 0);
}
[Test]
public void TestRulesetRevertedOnExitIfNoSelection()
{
AddStep("change ruleset", () => Ruleset.Value = new CatchRuleset().RulesetInfo);
AddStep("exit song select", () => songSelect.Exit());
AddAssert("ruleset reverted", () => Ruleset.Value.Equals(new OsuRuleset().RulesetInfo));
}
[Test]
public void TestBeatmapConfirmed()
{
@ -152,8 +120,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
public new BeatmapCarousel Carousel => base.Carousel;
public TestMultiplayerMatchSongSelect(Room room, WorkingBeatmap beatmap = null, RulesetInfo ruleset = null)
: base(room, null, beatmap, ruleset)
public TestMultiplayerMatchSongSelect(Room room)
: base(room)
{
}
}

View File

@ -4,6 +4,7 @@
#nullable disable
using System.Linq;
using JetBrains.Annotations;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
@ -17,6 +18,8 @@ using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Rooms;
using osu.Game.Overlays;
using osu.Game.Overlays.Dialog;
using osu.Game.Overlays.Mods;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
@ -24,6 +27,7 @@ using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Taiko;
using osu.Game.Rulesets.Taiko.Mods;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.Menu;
using osu.Game.Screens.OnlinePlay;
using osu.Game.Screens.OnlinePlay.Match;
using osu.Game.Screens.OnlinePlay.Multiplayer;
@ -40,7 +44,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
private MultiplayerMatchSubScreen screen;
private BeatmapManager beatmaps;
private RulesetStore rulesets;
private BeatmapSetInfo importedSet;
public TestSceneMultiplayerMatchSubScreen()
@ -51,8 +54,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
[BackgroundDependencyLoader]
private void load(GameHost host, AudioManager audio)
{
Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default));
Dependencies.Cache(new RealmRulesetStore(Realm));
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default));
Dependencies.Cache(Realm);
beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
@ -60,16 +63,15 @@ namespace osu.Game.Tests.Visual.Multiplayer
importedSet = beatmaps.GetAllUsableBeatmapSets().First();
}
[SetUp]
public new void Setup() => Schedule(() =>
{
SelectedRoom.Value = new Room { Name = { Value = "Test Room" } };
});
[SetUpSteps]
public void SetupSteps()
{
AddStep("load match", () => LoadScreen(screen = new MultiplayerMatchSubScreen(SelectedRoom.Value)));
AddStep("load match", () =>
{
SelectedRoom.Value = new Room { Name = { Value = "Test Room" } };
LoadScreen(screen = new TestMultiplayerMatchSubScreen(SelectedRoom.Value));
});
AddUntilStep("wait for load", () => screen.IsCurrentScreen());
}
@ -283,5 +285,29 @@ namespace osu.Game.Tests.Visual.Multiplayer
return lastItem.IsSelectedItem;
});
}
private class TestMultiplayerMatchSubScreen : MultiplayerMatchSubScreen
{
[Resolved(canBeNull: true)]
[CanBeNull]
private IDialogOverlay dialogOverlay { get; set; }
public TestMultiplayerMatchSubScreen(Room room)
: base(room)
{
}
public override bool OnExiting(ScreenExitEvent e)
{
// For testing purposes allow the screen to exit without confirming on second attempt.
if (!ExitConfirmed && dialogOverlay?.CurrentDialog is ConfirmDiscardChangesDialog confirmDialog)
{
confirmDialog.PerformAction<PopupDialogDangerousButton>();
return true;
}
return base.OnExiting(e);
}
}
}
}

View File

@ -9,6 +9,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Graphics.Cursor;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online;
using osu.Game.Online.API.Requests.Responses;
@ -368,12 +369,16 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
ParticipantsList? participantsList = null;
AddStep("create new list", () => Child = participantsList = new ParticipantsList
AddStep("create new list", () => Child = new OsuContextMenuContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Y,
Size = new Vector2(380, 0.7f)
RelativeSizeAxes = Axes.Both,
Child = participantsList = new ParticipantsList
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Y,
Size = new Vector2(380, 0.7f)
}
});
AddUntilStep("wait for list to load", () => participantsList?.IsLoaded == true);

View File

@ -31,33 +31,33 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
private MultiplayerPlaylist list;
private BeatmapManager beatmaps;
private RulesetStore rulesets;
private BeatmapSetInfo importedSet;
private BeatmapInfo importedBeatmap;
[BackgroundDependencyLoader]
private void load(GameHost host, AudioManager audio)
{
Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default));
Dependencies.Cache(new RealmRulesetStore(Realm));
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default));
Dependencies.Cache(Realm);
}
[SetUp]
public new void Setup() => Schedule(() =>
{
Child = list = new MultiplayerPlaylist
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Size = new Vector2(0.4f, 0.8f)
};
});
[SetUpSteps]
public new void SetUpSteps()
public override void SetUpSteps()
{
base.SetUpSteps();
AddStep("create list", () =>
{
Child = list = new MultiplayerPlaylist
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Size = new Vector2(0.4f, 0.8f)
};
});
AddStep("import beatmap", () =>
{
beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();

View File

@ -29,15 +29,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
private MultiplayerQueueList playlist;
private BeatmapManager beatmaps;
private RulesetStore rulesets;
private BeatmapSetInfo importedSet;
private BeatmapInfo importedBeatmap;
[BackgroundDependencyLoader]
private void load(GameHost host, AudioManager audio)
{
Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, API, audio, Resources, host, Beatmap.Default));
Dependencies.Cache(new RealmRulesetStore(Realm));
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, API, audio, Resources, host, Beatmap.Default));
Dependencies.Cache(Realm);
}
@ -98,14 +97,23 @@ namespace osu.Game.Tests.Visual.Multiplayer
}
[Test]
public void TestCurrentItemDoesNotHaveDeleteButton()
public void TestSingleItemDoesNotHaveDeleteButton()
{
AddStep("set all players queue mode", () => MultiplayerClient.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers }).WaitSafely());
AddUntilStep("wait for queue mode change", () => MultiplayerClient.ClientAPIRoom?.QueueMode.Value == QueueMode.AllPlayers);
assertDeleteButtonVisibility(0, false);
}
[Test]
public void TestCurrentItemHasDeleteButtonIfNotSingle()
{
AddStep("set all players queue mode", () => MultiplayerClient.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers }).WaitSafely());
AddUntilStep("wait for queue mode change", () => MultiplayerClient.ClientAPIRoom?.QueueMode.Value == QueueMode.AllPlayers);
addPlaylistItem(() => API.LocalUser.Value.OnlineID);
assertDeleteButtonVisibility(0, false);
assertDeleteButtonVisibility(0, true);
assertDeleteButtonVisibility(1, true);
AddStep("finish current item", () => MultiplayerClient.FinishCurrentItem().WaitSafely());

View File

@ -35,55 +35,58 @@ namespace osu.Game.Tests.Visual.Multiplayer
private BeatmapSetInfo importedSet;
private BeatmapManager beatmaps;
private RulesetStore rulesets;
[BackgroundDependencyLoader]
private void load(GameHost host, AudioManager audio)
{
Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default));
Dependencies.Cache(new RealmRulesetStore(Realm));
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default));
Dependencies.Cache(Realm);
beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
}
[SetUp]
public new void Setup() => Schedule(() =>
public override void SetUpSteps()
{
AvailabilityTracker.SelectedItem.BindTo(selectedItem);
base.SetUpSteps();
importedSet = beatmaps.GetAllUsableBeatmapSets().First();
Beatmap.Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First());
selectedItem.Value = new PlaylistItem(Beatmap.Value.BeatmapInfo)
AddStep("create button", () =>
{
RulesetID = Beatmap.Value.BeatmapInfo.Ruleset.OnlineID,
};
AvailabilityTracker.SelectedItem.BindTo(selectedItem);
Child = new PopoverContainer
{
RelativeSizeAxes = Axes.Both,
Child = new FillFlowContainer
importedSet = beatmaps.GetAllUsableBeatmapSets().First();
Beatmap.Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First());
selectedItem.Value = new PlaylistItem(Beatmap.Value.BeatmapInfo)
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Children = new Drawable[]
RulesetID = Beatmap.Value.BeatmapInfo.Ruleset.OnlineID,
};
Child = new PopoverContainer
{
RelativeSizeAxes = Axes.Both,
Child = new FillFlowContainer
{
spectateButton = new MultiplayerSpectateButton
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(200, 50),
},
startControl = new MatchStartControl
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(200, 50),
spectateButton = new MultiplayerSpectateButton
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(200, 50),
},
startControl = new MatchStartControl
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(200, 50),
}
}
}
}
};
});
};
});
}
[TestCase(MultiplayerRoomState.Open)]
[TestCase(MultiplayerRoomState.WaitingForLoad)]

View File

@ -28,15 +28,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
private BeatmapManager manager;
private RulesetStore rulesets;
private TestPlaylistsSongSelect songSelect;
[BackgroundDependencyLoader]
private void load(GameHost host, AudioManager audio)
{
Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default));
Dependencies.Cache(new RealmRulesetStore(Realm));
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default));
Dependencies.Cache(Realm);
var beatmapSet = TestResources.CreateTestBeatmapSetInfo();

View File

@ -14,17 +14,21 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneStarRatingRangeDisplay : OnlinePlayTestScene
{
[SetUp]
public new void Setup() => Schedule(() =>
public override void SetUpSteps()
{
SelectedRoom.Value = new Room();
base.SetUpSteps();
Child = new StarRatingRangeDisplay
AddStep("create display", () =>
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre
};
});
SelectedRoom.Value = new Room();
Child = new StarRatingRangeDisplay
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre
};
});
}
[Test]
public void TestRange([Values(0, 2, 3, 4, 6, 7)] double min, [Values(0, 2, 3, 4, 6, 7)] double max)

View File

@ -30,7 +30,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
public class TestSceneTeamVersus : ScreenTestScene
{
private BeatmapManager beatmaps;
private RulesetStore rulesets;
private BeatmapSetInfo importedSet;
private TestMultiplayerComponents multiplayerComponents;
@ -40,8 +39,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
[BackgroundDependencyLoader]
private void load(GameHost host, AudioManager audio)
{
Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default));
Dependencies.Cache(new RealmRulesetStore(Realm));
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default));
Dependencies.Cache(Realm);
}

View File

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

View File

@ -21,7 +21,7 @@ namespace osu.Game.Tests.Visual.Navigation
[Test]
public void TestEditDefaultSkin()
{
AddAssert("is default skin", () => skinManager.CurrentSkinInfo.Value.ID == SkinInfo.DEFAULT_SKIN);
AddAssert("is default skin", () => skinManager.CurrentSkinInfo.Value.ID == SkinInfo.ARGON_SKIN);
AddStep("open settings", () => { Game.Settings.Show(); });
@ -32,7 +32,7 @@ namespace osu.Game.Tests.Visual.Navigation
AddStep("open skin editor", () => skinEditor.Show());
// Until step required as the skin editor may take time to load (and an extra scheduled frame for the mutable part).
AddUntilStep("is modified default skin", () => skinManager.CurrentSkinInfo.Value.ID != SkinInfo.DEFAULT_SKIN);
AddUntilStep("is modified default skin", () => skinManager.CurrentSkinInfo.Value.ID != SkinInfo.ARGON_SKIN);
AddAssert("is not protected", () => skinManager.CurrentSkinInfo.Value.PerformRead(s => !s.Protected));
AddUntilStep("export button enabled", () => Game.Settings.ChildrenOfType<SkinSection.ExportSkinButton>().SingleOrDefault()?.Enabled.Value == true);

View File

@ -26,16 +26,7 @@ namespace osu.Game.Tests.Visual.Navigation
public void TestImportantNotificationDoesntInterruptSetup()
{
AddStep("post important notification", () => Game.Notifications.Post(new SimpleNotification { Text = "Important notification" }));
AddAssert("no notification posted", () => Game.Notifications.UnreadCount.Value == 0);
AddAssert("first-run setup still visible", () => Game.FirstRunOverlay.State.Value == Visibility.Visible);
AddUntilStep("finish first-run setup", () =>
{
Game.FirstRunOverlay.NextButton.TriggerClick();
return Game.FirstRunOverlay.State.Value == Visibility.Hidden;
});
AddWaitStep("wait for post delay", 5);
AddAssert("notifications shown", () => Game.Notifications.State.Value == Visibility.Visible);
AddAssert("notification posted", () => Game.Notifications.UnreadCount.Value == 1);
}

View File

@ -25,6 +25,7 @@ using osu.Game.Rulesets.Mods;
using osu.Game.Scoring;
using osu.Game.Screens.Menu;
using osu.Game.Skinning;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Navigation
{
@ -79,6 +80,16 @@ namespace osu.Game.Tests.Visual.Navigation
[Resolved]
private OsuGameBase gameBase { get; set; }
[Test]
public void TestCursorHidesWhenIdle()
{
AddStep("click mouse", () => InputManager.Click(MouseButton.Left));
AddUntilStep("wait until idle", () => Game.IsIdle.Value);
AddUntilStep("menu cursor hidden", () => Game.GlobalCursorDisplay.MenuCursor.ActiveCursor.Alpha == 0);
AddStep("click mouse", () => InputManager.Click(MouseButton.Left));
AddUntilStep("menu cursor shown", () => Game.GlobalCursorDisplay.MenuCursor.ActiveCursor.Alpha == 1);
}
[Test]
public void TestNullRulesetHandled()
{

View File

@ -9,8 +9,10 @@ using NUnit.Framework;
using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Online.API;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Catch;
using osu.Game.Rulesets.Mania;
using osu.Game.Rulesets.Osu;
using osu.Game.Scoring;
@ -92,6 +94,31 @@ namespace osu.Game.Tests.Visual.Navigation
returnToMenu();
}
[Test]
public void TestFromSongSelectWithFilter([Values] ScorePresentType type)
{
AddStep("enter song select", () => Game.ChildrenOfType<ButtonSystem>().Single().OnSolo.Invoke());
AddUntilStep("song select is current", () => Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect && songSelect.BeatmapSetsLoaded);
AddStep("filter to nothing", () => ((PlaySongSelect)Game.ScreenStack.CurrentScreen).FilterControl.CurrentTextSearch.Value = "fdsajkl;fgewq");
AddUntilStep("wait for no results", () => Beatmap.IsDefault);
var firstImport = importScore(1, new CatchRuleset().RulesetInfo);
presentAndConfirm(firstImport, type);
}
[Test]
public void TestFromSongSelectWithConvertRulesetChange([Values] ScorePresentType type)
{
AddStep("enter song select", () => Game.ChildrenOfType<ButtonSystem>().Single().OnSolo.Invoke());
AddUntilStep("song select is current", () => Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect && songSelect.BeatmapSetsLoaded);
AddStep("set convert to false", () => Game.LocalConfig.SetValue(OsuSetting.ShowConvertedBeatmaps, false));
var firstImport = importScore(1, new CatchRuleset().RulesetInfo);
presentAndConfirm(firstImport, type);
}
[Test]
public void TestFromSongSelect([Values] ScorePresentType type)
{

View File

@ -26,6 +26,7 @@ using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Scoring;
using osu.Game.Screens.Menu;
using osu.Game.Screens.OnlinePlay.Lounge;
using osu.Game.Screens.OnlinePlay.Playlists;
using osu.Game.Screens.Play;
using osu.Game.Screens.Ranking;
using osu.Game.Screens.Select;
@ -45,6 +46,57 @@ namespace osu.Game.Tests.Visual.Navigation
private Vector2 optionsButtonPosition => Game.ToScreenSpace(new Vector2(click_padding, click_padding));
[TestCase(false)]
[TestCase(true)]
public void TestConfirmationRequiredToDiscardPlaylist(bool withPlaylistItemAdded)
{
Screens.OnlinePlay.Playlists.Playlists playlistScreen = null;
AddUntilStep("wait for dialog overlay", () => Game.ChildrenOfType<DialogOverlay>().SingleOrDefault() != null);
PushAndConfirm(() => playlistScreen = new Screens.OnlinePlay.Playlists.Playlists());
AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely());
AddStep("open create screen", () =>
{
InputManager.MoveMouseTo(playlistScreen.ChildrenOfType<CreatePlaylistsRoomButton>().Single());
InputManager.Click(MouseButton.Left);
});
if (withPlaylistItemAdded)
{
AddUntilStep("wait for settings displayed",
() => (playlistScreen.CurrentSubScreen as PlaylistsRoomSubScreen)?.ChildrenOfType<PlaylistsRoomSettingsOverlay>().SingleOrDefault()?.State.Value == Visibility.Visible);
AddStep("edit playlist", () => InputManager.Key(Key.Enter));
AddUntilStep("wait for song select", () => (playlistScreen.CurrentSubScreen as PlaylistsSongSelect)?.BeatmapSetsLoaded == true);
AddUntilStep("wait for selection", () => !Game.Beatmap.IsDefault);
AddStep("add item", () => InputManager.Key(Key.Enter));
AddUntilStep("wait for return to playlist screen", () => playlistScreen.CurrentSubScreen is PlaylistsRoomSubScreen);
pushEscape();
AddAssert("confirmation dialog shown", () => Game.ChildrenOfType<DialogOverlay>().Single().CurrentDialog is not null);
AddStep("confirm exit", () => InputManager.Key(Key.Enter));
AddAssert("dialog dismissed", () => Game.ChildrenOfType<DialogOverlay>().Single().CurrentDialog == null);
exitViaEscapeAndConfirm();
}
else
{
pushEscape();
AddAssert("confirmation dialog not shown", () => Game.ChildrenOfType<DialogOverlay>().Single().CurrentDialog == null);
exitViaEscapeAndConfirm();
}
}
[Test]
public void TestExitSongSelectWithEscape()
{
@ -74,14 +126,14 @@ namespace osu.Game.Tests.Visual.Navigation
AddStep("set filter again", () => songSelect.ChildrenOfType<SearchTextBox>().Single().Current.Value = "test");
AddStep("open collections dropdown", () =>
{
InputManager.MoveMouseTo(songSelect.ChildrenOfType<CollectionFilterDropdown>().Single());
InputManager.MoveMouseTo(songSelect.ChildrenOfType<CollectionDropdown>().Single());
InputManager.Click(MouseButton.Left);
});
AddStep("press back once", () => InputManager.Click(MouseButton.Button1));
AddAssert("still at song select", () => Game.ScreenStack.CurrentScreen == songSelect);
AddAssert("collections dropdown closed", () => songSelect
.ChildrenOfType<CollectionFilterDropdown>().Single()
.ChildrenOfType<CollectionDropdown>().Single()
.ChildrenOfType<Dropdown<CollectionFilterMenuItem>.DropdownMenu>().Single().State == MenuState.Closed);
AddStep("press back a second time", () => InputManager.Click(MouseButton.Button1));

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.
#nullable disable
using System.Linq;
using NUnit.Framework;
using osu.Framework.Testing;
@ -13,7 +11,7 @@ namespace osu.Game.Tests.Visual.Navigation
{
public class TestSceneStartupImport : OsuGameTestScene
{
private string importFilename;
private string? importFilename;
protected override TestOsuGame CreateTestGame() => new TestOsuGame(LocalStorage, API, new[] { importFilename });

View File

@ -34,7 +34,7 @@ namespace osu.Game.Tests.Visual.Navigation
AddStep("force save config", () => Game.LocalConfig.Save());
AddStep("remove game", () => Remove(Game));
AddStep("remove game", () => Remove(Game, true));
AddStep("create game again", CreateGame);

View File

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

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