Merge branch 'master' into non-concurrent-sample-playback

This commit is contained in:
smoogipoo
2021-02-12 17:22:15 +09:00
295 changed files with 7478 additions and 2398 deletions

View File

@ -5,6 +5,8 @@ using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Components.Timelines.Summary;
using osuTK;
@ -13,6 +15,9 @@ namespace osu.Game.Tests.Visual.Editing
[TestFixture]
public class TestSceneEditorSummaryTimeline : EditorClockTestScene
{
[Cached(typeof(EditorBeatmap))]
private readonly EditorBeatmap editorBeatmap = new EditorBeatmap(new OsuBeatmap());
[BackgroundDependencyLoader]
private void load()
{

View File

@ -0,0 +1,55 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit.Compose.Components.Timeline;
using osuTK;
using osuTK.Input;
using static osu.Game.Screens.Edit.Compose.Components.Timeline.TimelineHitObjectBlueprint;
namespace osu.Game.Tests.Visual.Editing
{
public class TestSceneTimelineHitObjectBlueprint : TimelineTestScene
{
public override Drawable CreateTestComponent() => new TimelineBlueprintContainer(Composer);
[Test]
public void TestDisallowZeroDurationObjects()
{
DragBar dragBar;
AddStep("add spinner", () =>
{
EditorBeatmap.Clear();
EditorBeatmap.Add(new Spinner
{
Position = new Vector2(256, 256),
StartTime = 150,
Duration = 500
});
});
AddStep("hold down drag bar", () =>
{
// distinguishes between the actual drag bar and its "underlay shadow".
dragBar = this.ChildrenOfType<DragBar>().Single(bar => bar.HandlePositionalInput);
InputManager.MoveMouseTo(dragBar);
InputManager.PressButton(MouseButton.Left);
});
AddStep("try to drag bar past start", () =>
{
var blueprint = this.ChildrenOfType<TimelineHitObjectBlueprint>().Single();
InputManager.MoveMouseTo(blueprint.SelectionQuad.TopLeft - new Vector2(100, 0));
InputManager.ReleaseButton(MouseButton.Left);
});
AddAssert("object has non-zero duration", () => EditorBeatmap.HitObjects.OfType<IHasDuration>().Single().Duration > 0);
}
}
}

View File

@ -23,22 +23,24 @@ namespace osu.Game.Tests.Visual.Editing
protected HitObjectComposer Composer { get; private set; }
protected EditorBeatmap EditorBeatmap { get; private set; }
[BackgroundDependencyLoader]
private void load(AudioManager audio)
{
Beatmap.Value = new WaveformTestBeatmap(audio);
var playable = Beatmap.Value.GetPlayableBeatmap(Beatmap.Value.BeatmapInfo.Ruleset);
var editorBeatmap = new EditorBeatmap(playable);
EditorBeatmap = new EditorBeatmap(playable);
Dependencies.Cache(editorBeatmap);
Dependencies.CacheAs<IBeatSnapProvider>(editorBeatmap);
Dependencies.Cache(EditorBeatmap);
Dependencies.CacheAs<IBeatSnapProvider>(EditorBeatmap);
Composer = playable.BeatmapInfo.Ruleset.CreateInstance().CreateHitObjectComposer().With(d => d.Alpha = 0);
AddRange(new Drawable[]
{
editorBeatmap,
EditorBeatmap,
Composer,
new FillFlowContainer
{

View File

@ -17,10 +17,12 @@ using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Objects.Legacy;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.UI;
using osuTK;
using osuTK.Graphics;
@ -129,6 +131,31 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("no DHOs shown", () => !this.ChildrenOfType<DrawableTestHitObject>().Any());
}
[Test]
public void TestApplyHitResultOnKilled()
{
ManualClock clock = null;
bool anyJudged = false;
void onNewResult(JudgementResult _) => anyJudged = true;
var beatmap = new Beatmap();
beatmap.HitObjects.Add(new TestKilledHitObject { Duration = 20 });
createTest(beatmap, 10, () => new FramedClock(clock = new ManualClock()));
AddStep("subscribe to new result", () =>
{
anyJudged = false;
drawableRuleset.NewResult += onNewResult;
});
AddStep("skip past object", () => clock.CurrentTime = beatmap.HitObjects[0].GetEndTime() + 1000);
AddAssert("object judged", () => anyJudged);
AddStep("clean up", () => drawableRuleset.NewResult -= onNewResult);
}
private void createTest(IBeatmap beatmap, int poolSize, Func<IFrameBasedClock> createClock = null) => AddStep("create test", () =>
{
var ruleset = new TestPoolingRuleset();
@ -192,6 +219,7 @@ namespace osu.Game.Tests.Visual.Gameplay
private void load()
{
RegisterPool<TestHitObject, DrawableTestHitObject>(poolSize);
RegisterPool<TestKilledHitObject, DrawableTestKilledHitObject>(poolSize);
}
protected override HitObjectLifetimeEntry CreateLifetimeEntry(HitObject hitObject) => new TestHitObjectLifetimeEntry(hitObject);
@ -220,19 +248,30 @@ namespace osu.Game.Tests.Visual.Gameplay
protected override IEnumerable<TestHitObject> ConvertHitObject(HitObject original, IBeatmap beatmap, CancellationToken cancellationToken)
{
yield return new TestHitObject
switch (original)
{
StartTime = original.StartTime,
Duration = 250
};
case TestKilledHitObject h:
yield return h;
break;
default:
yield return new TestHitObject
{
StartTime = original.StartTime,
Duration = 250
};
break;
}
}
}
#endregion
#region HitObject
#region HitObjects
private class TestHitObject : ConvertHitObject
private class TestHitObject : ConvertHitObject, IHasDuration
{
public double EndTime => StartTime + Duration;
@ -287,6 +326,30 @@ namespace osu.Game.Tests.Visual.Gameplay
}
}
private class TestKilledHitObject : TestHitObject
{
}
private class DrawableTestKilledHitObject : DrawableHitObject<TestKilledHitObject>
{
public DrawableTestKilledHitObject()
: base(null)
{
}
protected override void UpdateHitStateTransforms(ArmedState state)
{
base.UpdateHitStateTransforms(state);
Expire();
}
public override void OnKilled()
{
base.OnKilled();
ApplyResult(r => r.Type = r.Judgement.MinResult);
}
}
#endregion
}
}

View File

@ -19,7 +19,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{
var beatmap = Beatmap.Value.GetPlayableBeatmap(ruleset.RulesetInfo, Array.Empty<Mod>());
return new ScoreAccessibleReplayPlayer(ruleset.GetAutoplayMod()?.CreateReplayScore(beatmap));
return new ScoreAccessibleReplayPlayer(ruleset.GetAutoplayMod()?.CreateReplayScore(beatmap, Array.Empty<Mod>()));
}
protected override void AddCheckSteps()

View File

@ -0,0 +1,74 @@
// 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.Testing;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
using osu.Game.Storyboards;
using osu.Game.Storyboards.Drawables;
namespace osu.Game.Tests.Visual.Gameplay
{
public class TestSceneStoryboardSamplePlayback : PlayerTestScene
{
private Storyboard storyboard;
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
{
config.Set(OsuSetting.ShowStoryboard, true);
storyboard = new Storyboard();
var backgroundLayer = storyboard.GetLayer("Background");
backgroundLayer.Add(new StoryboardSampleInfo("Intro/welcome.mp3", time: -7000, volume: 20));
backgroundLayer.Add(new StoryboardSampleInfo("Intro/welcome.mp3", time: -5000, volume: 20));
backgroundLayer.Add(new StoryboardSampleInfo("Intro/welcome.mp3", time: 0, volume: 20));
}
[Test]
public void TestStoryboardSamplesStopDuringPause()
{
checkForFirstSamplePlayback();
AddStep("player paused", () => Player.Pause());
AddAssert("player is currently paused", () => Player.GameplayClockContainer.IsPaused.Value);
AddAssert("all storyboard samples stopped immediately", () => allStoryboardSamples.All(sound => !sound.IsPlaying));
AddStep("player resume", () => Player.Resume());
AddUntilStep("any storyboard samples playing after resume", () => allStoryboardSamples.Any(sound => sound.IsPlaying));
}
[Test]
public void TestStoryboardSamplesStopOnSkip()
{
checkForFirstSamplePlayback();
AddStep("skip intro", () => InputManager.Key(osuTK.Input.Key.Space));
AddAssert("all storyboard samples stopped immediately", () => allStoryboardSamples.All(sound => !sound.IsPlaying));
AddUntilStep("any storyboard samples playing after skip", () => allStoryboardSamples.Any(sound => sound.IsPlaying));
}
private void checkForFirstSamplePlayback()
{
AddUntilStep("storyboard loaded", () => Player.Beatmap.Value.StoryboardLoaded);
AddUntilStep("any storyboard samples playing", () => allStoryboardSamples.Any(sound => sound.IsPlaying));
}
private IEnumerable<DrawableStoryboardSample> allStoryboardSamples => Player.ChildrenOfType<DrawableStoryboardSample>();
protected override bool AllowFail => false;
protected override Ruleset CreatePlayerRuleset() => new OsuRuleset();
protected override TestPlayer CreatePlayer(Ruleset ruleset) => new TestPlayer(true, false);
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) =>
new ClockBackedTestWorkingBeatmap(beatmap, storyboard ?? this.storyboard, Clock, Audio);
}
}

View File

@ -11,8 +11,8 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Platform;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.Rooms;
using osu.Game.Overlays;
using osu.Game.Rulesets;
@ -20,6 +20,7 @@ using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Screens.OnlinePlay;
using osu.Game.Tests.Beatmaps;
using osu.Game.Users;
using osuTK;
using osuTK.Input;
@ -241,7 +242,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
}
private void moveToItem(int index, Vector2? offset = null)
=> AddStep($"move mouse to item {index}", () => InputManager.MoveMouseTo(playlist.ChildrenOfType<OsuRearrangeableListItem<PlaylistItem>>().ElementAt(index), offset));
=> AddStep($"move mouse to item {index}", () => InputManager.MoveMouseTo(playlist.ChildrenOfType<DifficultyIcon>().ElementAt(index), offset));
private void moveToDragger(int index, Vector2? offset = null) => AddStep($"move mouse to dragger {index}", () =>
{
@ -252,7 +253,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
private void moveToDeleteButton(int index, Vector2? offset = null) => AddStep($"move mouse to delete button {index}", () =>
{
var item = playlist.ChildrenOfType<OsuRearrangeableListItem<PlaylistItem>>().ElementAt(index);
InputManager.MoveMouseTo(item.ChildrenOfType<IconButton>().ElementAt(0), offset);
InputManager.MoveMouseTo(item.ChildrenOfType<DrawableRoomPlaylistItem.PlaylistRemoveButton>().ElementAt(0), offset);
});
private void assertHandleVisibility(int index, bool visible)
@ -260,7 +261,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
() => (playlist.ChildrenOfType<OsuRearrangeableListItem<PlaylistItem>.PlaylistItemHandle>().ElementAt(index).Alpha > 0) == visible);
private void assertDeleteButtonVisibility(int index, bool visible)
=> AddAssert($"delete button {index} {(visible ? "is" : "is not")} visible", () => (playlist.ChildrenOfType<IconButton>().ElementAt(2 + index * 2).Alpha > 0) == visible);
=> AddAssert($"delete button {index} {(visible ? "is" : "is not")} visible", () => (playlist.ChildrenOfType<DrawableRoomPlaylistItem.PlaylistRemoveButton>().ElementAt(2 + index * 2).Alpha > 0) == visible);
private void createPlaylist(bool allowEdit, bool allowSelection)
{
@ -278,7 +279,21 @@ namespace osu.Game.Tests.Visual.Multiplayer
playlist.Items.Add(new PlaylistItem
{
ID = i,
Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo },
Beatmap =
{
Value = i % 2 == 1
? new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo
: new BeatmapInfo
{
Metadata = new BeatmapMetadata
{
Artist = "Artist",
Author = new User { Username = "Creator name here" },
Title = "Long title used to check background colour",
},
BeatmapSet = new BeatmapSetInfo()
}
},
Ruleset = { Value = new OsuRuleset().RulesetInfo },
RequiredMods =
{

View File

@ -0,0 +1,21 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
using osu.Framework.Graphics.Containers;
using osu.Game.Screens.OnlinePlay;
namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneFreeModSelectOverlay : MultiplayerTestScene
{
[SetUp]
public new void Setup() => Schedule(() =>
{
Child = new FreeModSelectOverlay
{
State = { Value = Visibility.Visible }
};
});
}
}

View File

@ -7,7 +7,12 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Rooms;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Screens.OnlinePlay.Multiplayer.Participants;
using osu.Game.Users;
using osuTK;
@ -17,16 +22,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
public class TestSceneMultiplayerParticipantsList : MultiplayerTestScene
{
[SetUp]
public new void Setup() => Schedule(() =>
{
Child = new ParticipantsList
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Y,
Size = new Vector2(380, 0.7f)
};
});
public new void Setup() => Schedule(createNewParticipantsList);
[Test]
public void TestAddUser()
@ -73,6 +69,50 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddAssert("single panel is for second user", () => this.ChildrenOfType<ParticipantPanel>().Single().User.User == secondUser);
}
[Test]
public void TestGameStateHasPriorityOverDownloadState()
{
AddStep("set to downloading map", () => Client.ChangeBeatmapAvailability(BeatmapAvailability.Downloading(0)));
checkProgressBarVisibility(true);
AddStep("make user ready", () => Client.ChangeState(MultiplayerUserState.Results));
checkProgressBarVisibility(false);
AddUntilStep("ready mark visible", () => this.ChildrenOfType<StateDisplay>().Single().IsPresent);
AddStep("make user ready", () => Client.ChangeState(MultiplayerUserState.Idle));
checkProgressBarVisibility(true);
}
[Test]
public void TestCorrectInitialState()
{
AddStep("set to downloading map", () => Client.ChangeBeatmapAvailability(BeatmapAvailability.Downloading(0)));
AddStep("recreate list", createNewParticipantsList);
checkProgressBarVisibility(true);
}
[Test]
public void TestBeatmapDownloadingStates()
{
AddStep("set to no map", () => Client.ChangeBeatmapAvailability(BeatmapAvailability.NotDownloaded()));
AddStep("set to downloading map", () => Client.ChangeBeatmapAvailability(BeatmapAvailability.Downloading(0)));
checkProgressBarVisibility(true);
AddRepeatStep("increment progress", () =>
{
var progress = this.ChildrenOfType<ParticipantPanel>().Single().User.BeatmapAvailability.DownloadProgress ?? 0;
Client.ChangeBeatmapAvailability(BeatmapAvailability.Downloading(progress + RNG.NextSingle(0.1f)));
}, 25);
AddAssert("progress bar increased", () => this.ChildrenOfType<ProgressBar>().Single().Current.Value > 0);
AddStep("set to importing map", () => Client.ChangeBeatmapAvailability(BeatmapAvailability.Importing()));
checkProgressBarVisibility(false);
AddStep("set to available", () => Client.ChangeBeatmapAvailability(BeatmapAvailability.LocallyAvailable()));
}
[Test]
public void TestToggleReadyState()
{
@ -120,8 +160,64 @@ namespace osu.Game.Tests.Visual.Multiplayer
});
Client.ChangeUserState(i, (MultiplayerUserState)RNG.Next(0, (int)MultiplayerUserState.Results + 1));
if (RNG.NextBool())
{
var beatmapState = (DownloadState)RNG.Next(0, (int)DownloadState.LocallyAvailable + 1);
switch (beatmapState)
{
case DownloadState.NotDownloaded:
Client.ChangeUserBeatmapAvailability(i, BeatmapAvailability.NotDownloaded());
break;
case DownloadState.Downloading:
Client.ChangeUserBeatmapAvailability(i, BeatmapAvailability.Downloading(RNG.NextSingle()));
break;
case DownloadState.Importing:
Client.ChangeUserBeatmapAvailability(i, BeatmapAvailability.Importing());
break;
}
}
}
});
}
[Test]
public void TestUserWithMods()
{
AddStep("add user", () =>
{
Client.AddUser(new User
{
Id = 0,
Username = "User 0",
CurrentModeRank = RNG.Next(1, 100000),
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
});
Client.ChangeUserMods(0, new Mod[]
{
new OsuModHardRock(),
new OsuModDifficultyAdjust { ApproachRate = { Value = 1 } }
});
});
for (var i = MultiplayerUserState.Idle; i < MultiplayerUserState.Results; i++)
{
var state = i;
AddStep($"set state: {state}", () => Client.ChangeUserState(0, state));
}
}
private void createNewParticipantsList()
{
Child = new ParticipantsList { Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Y, Size = new Vector2(380, 0.7f) };
}
private void checkProgressBarVisibility(bool visible) =>
AddUntilStep($"progress bar {(visible ? "is" : "is not")}visible", () =>
this.ChildrenOfType<ProgressBar>().Single().IsPresent == visible);
}
}

View File

@ -6,6 +6,7 @@ using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Platform;
using osu.Framework.Testing;
@ -26,8 +27,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
public class TestSceneMultiplayerReadyButton : MultiplayerTestScene
{
private MultiplayerReadyButton button;
private OnlinePlayBeatmapAvailablilityTracker beatmapTracker;
private BeatmapSetInfo importedSet;
private readonly Bindable<PlaylistItem> selectedItem = new Bindable<PlaylistItem>();
private BeatmapManager beatmaps;
private RulesetStore rulesets;
@ -39,6 +43,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, host, Beatmap.Default));
beatmaps.Import(TestResources.GetTestBeatmapForImport(true)).Wait();
Add(beatmapTracker = new OnlinePlayBeatmapAvailablilityTracker
{
SelectedItem = { BindTarget = selectedItem }
});
Dependencies.Cache(beatmapTracker);
}
[SetUp]
@ -46,20 +57,20 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First();
Beatmap.Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First());
selectedItem.Value = new PlaylistItem
{
Beatmap = { Value = Beatmap.Value.BeatmapInfo },
Ruleset = { Value = Beatmap.Value.BeatmapInfo.Ruleset },
};
Child = button = new MultiplayerReadyButton
if (button != null)
Remove(button);
Add(button = new MultiplayerReadyButton
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(200, 50),
SelectedItem =
{
Value = new PlaylistItem
{
Beatmap = { Value = Beatmap.Value.BeatmapInfo },
Ruleset = { Value = Beatmap.Value.BeatmapInfo.Ruleset }
}
},
OnReadyClick = async () =>
{
readyClickOperation = OngoingOperationTracker.BeginOperation();
@ -73,7 +84,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
await Client.ToggleReady();
readyClickOperation.Dispose();
}
};
});
});
[Test]

View File

@ -101,7 +101,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
});
});
AddAssert("multiplayer room joined", () => roomContainer.Client.Room != null);
AddUntilStep("multiplayer room joined", () => roomContainer.Client.Room != null);
}
[Test]
@ -133,7 +133,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
});
});
AddAssert("multiplayer room joined", () => roomContainer.Client.Room != null);
AddUntilStep("multiplayer room joined", () => roomContainer.Client.Room != null);
}
private TestMultiplayerRoomManager createRoomManager()

View File

@ -19,11 +19,11 @@ using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Screens.OnlinePlay.Components;
using osu.Game.Screens.Select;
using osu.Game.Screens.OnlinePlay.Playlists;
namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneMatchSongSelect : RoomTestScene
public class TestScenePlaylistsSongSelect : RoomTestScene
{
[Resolved]
private BeatmapManager beatmapManager { get; set; }
@ -32,7 +32,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
private RulesetStore rulesets;
private TestMatchSongSelect songSelect;
private TestPlaylistsSongSelect songSelect;
[BackgroundDependencyLoader]
private void load(GameHost host, AudioManager audio)
@ -87,9 +87,10 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
Ruleset.Value = new OsuRuleset().RulesetInfo;
Beatmap.SetDefault();
SelectedMods.Value = Array.Empty<Mod>();
});
AddStep("create song select", () => LoadScreen(songSelect = new TestMatchSongSelect()));
AddStep("create song select", () => LoadScreen(songSelect = new TestPlaylistsSongSelect()));
AddUntilStep("wait for present", () => songSelect.IsCurrentScreen());
}
@ -176,7 +177,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddAssert("item has rate 1.5", () => Precision.AlmostEquals(1.5, ((OsuModDoubleTime)Room.Playlist.First().RequiredMods[0]).SpeedChange.Value));
}
private class TestMatchSongSelect : MatchSongSelect
private class TestPlaylistsSongSelect : PlaylistsSongSelect
{
public new MatchBeatmapDetailArea BeatmapDetails => (MatchBeatmapDetailArea)base.BeatmapDetails;
}

View File

@ -1,32 +1,81 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Overlays;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays;
using osu.Game.Overlays.BeatmapListing;
using osu.Game.Rulesets;
namespace osu.Game.Tests.Visual.Online
{
public class TestSceneBeatmapListingOverlay : OsuTestScene
{
protected override bool UseOnlineAPI => true;
private readonly List<APIBeatmapSet> setsForResponse = new List<APIBeatmapSet>();
private readonly BeatmapListingOverlay overlay;
private BeatmapListingOverlay overlay;
public TestSceneBeatmapListingOverlay()
[BackgroundDependencyLoader]
private void load()
{
Add(overlay = new BeatmapListingOverlay());
Child = overlay = new BeatmapListingOverlay { State = { Value = Visibility.Visible } };
((DummyAPIAccess)API).HandleRequest = req =>
{
if (req is SearchBeatmapSetsRequest searchBeatmapSetsRequest)
{
searchBeatmapSetsRequest.TriggerSuccess(new SearchBeatmapSetsResponse
{
BeatmapSets = setsForResponse,
});
}
};
}
[Test]
public void TestShow()
public void TestNoBeatmapsPlaceholder()
{
AddStep("Show", overlay.Show);
AddStep("fetch for 0 beatmaps", () => fetchFor());
AddUntilStep("placeholder shown", () => overlay.ChildrenOfType<BeatmapListingOverlay.NotFoundDrawable>().SingleOrDefault()?.IsPresent == true);
AddStep("fetch for 1 beatmap", () => fetchFor(CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet));
AddUntilStep("placeholder hidden", () => !overlay.ChildrenOfType<BeatmapListingOverlay.NotFoundDrawable>().Any());
AddStep("fetch for 0 beatmaps", () => fetchFor());
AddUntilStep("placeholder shown", () => overlay.ChildrenOfType<BeatmapListingOverlay.NotFoundDrawable>().SingleOrDefault()?.IsPresent == true);
// fetch once more to ensure nothing happens in displaying placeholder again when it already is present.
AddStep("fetch for 0 beatmaps again", () => fetchFor());
AddUntilStep("placeholder shown", () => overlay.ChildrenOfType<BeatmapListingOverlay.NotFoundDrawable>().SingleOrDefault()?.IsPresent == true);
}
[Test]
public void TestHide()
private void fetchFor(params BeatmapSetInfo[] beatmaps)
{
AddStep("Hide", overlay.Hide);
setsForResponse.Clear();
setsForResponse.AddRange(beatmaps.Select(b => new TestAPIBeatmapSet(b)));
// trigger arbitrary change for fetching.
overlay.ChildrenOfType<BeatmapListingSearchControl>().Single().Query.TriggerChange();
}
private class TestAPIBeatmapSet : APIBeatmapSet
{
private readonly BeatmapSetInfo beatmapSet;
public TestAPIBeatmapSet(BeatmapSetInfo beatmapSet)
{
this.beatmapSet = beatmapSet;
}
public override BeatmapSetInfo ToBeatmapSet(RulesetStore rulesets) => beatmapSet;
}
}
}

View File

@ -231,8 +231,8 @@ namespace osu.Game.Tests.Visual.Online
});
});
AddAssert("shown beatmaps of current ruleset", () => overlay.Header.Picker.Difficulties.All(b => b.Beatmap.Ruleset.Equals(overlay.Header.RulesetSelector.Current.Value)));
AddAssert("left-most beatmap selected", () => overlay.Header.Picker.Difficulties.First().State == BeatmapPicker.DifficultySelectorState.Selected);
AddAssert("shown beatmaps of current ruleset", () => overlay.Header.HeaderContent.Picker.Difficulties.All(b => b.Beatmap.Ruleset.Equals(overlay.Header.RulesetSelector.Current.Value)));
AddAssert("left-most beatmap selected", () => overlay.Header.HeaderContent.Picker.Difficulties.First().State == BeatmapPicker.DifficultySelectorState.Selected);
}
[Test]
@ -310,12 +310,12 @@ namespace osu.Game.Tests.Visual.Online
private void downloadAssert(bool shown)
{
AddAssert($"is download button {(shown ? "shown" : "hidden")}", () => overlay.Header.DownloadButtonsVisible == shown);
AddAssert($"is download button {(shown ? "shown" : "hidden")}", () => overlay.Header.HeaderContent.DownloadButtonsVisible == shown);
}
private class TestBeatmapSetOverlay : BeatmapSetOverlay
{
public new Header Header => base.Header;
public new BeatmapSetHeader Header => base.Header;
}
}
}

View File

@ -103,26 +103,26 @@ namespace osu.Game.Tests.Visual.Online
private void testLinksGeneral()
{
addMessageWithChecks("test!");
addMessageWithChecks("osu.ppy.sh!");
addMessageWithChecks("https://osu.ppy.sh!", 1, expectedActions: LinkAction.External);
addMessageWithChecks("dev.ppy.sh!");
addMessageWithChecks("https://dev.ppy.sh!", 1, expectedActions: LinkAction.External);
addMessageWithChecks("00:12:345 (1,2) - Test?", 1, expectedActions: LinkAction.OpenEditorTimestamp);
addMessageWithChecks("Wiki link for tasty [[Performance Points]]", 1, expectedActions: LinkAction.External);
addMessageWithChecks("(osu forums)[https://osu.ppy.sh/forum] (old link format)", 1, expectedActions: LinkAction.External);
addMessageWithChecks("[https://osu.ppy.sh/home New site] (new link format)", 1, expectedActions: LinkAction.External);
addMessageWithChecks("[osu forums](https://osu.ppy.sh/forum) (new link format 2)", 1, expectedActions: LinkAction.External);
addMessageWithChecks("[https://osu.ppy.sh/home This is only a link to the new osu webpage but this is supposed to test word wrap.]", 1, expectedActions: LinkAction.External);
addMessageWithChecks("is now listening to [https://osu.ppy.sh/s/93523 IMAGE -MATERIAL- <Version 0>]", 1, true, expectedActions: LinkAction.OpenBeatmapSet);
addMessageWithChecks("is now playing [https://osu.ppy.sh/b/252238 IMAGE -MATERIAL- <Version 0>]", 1, true, expectedActions: LinkAction.OpenBeatmap);
addMessageWithChecks("Let's (try)[https://osu.ppy.sh/home] [https://osu.ppy.sh/b/252238 multiple links] https://osu.ppy.sh/home", 3,
addMessageWithChecks("(osu forums)[https://dev.ppy.sh/forum] (old link format)", 1, expectedActions: LinkAction.External);
addMessageWithChecks("[https://dev.ppy.sh/home New site] (new link format)", 1, expectedActions: LinkAction.External);
addMessageWithChecks("[osu forums](https://dev.ppy.sh/forum) (new link format 2)", 1, expectedActions: LinkAction.External);
addMessageWithChecks("[https://dev.ppy.sh/home This is only a link to the new osu webpage but this is supposed to test word wrap.]", 1, expectedActions: LinkAction.External);
addMessageWithChecks("is now listening to [https://dev.ppy.sh/s/93523 IMAGE -MATERIAL- <Version 0>]", 1, true, expectedActions: LinkAction.OpenBeatmapSet);
addMessageWithChecks("is now playing [https://dev.ppy.sh/b/252238 IMAGE -MATERIAL- <Version 0>]", 1, true, expectedActions: LinkAction.OpenBeatmap);
addMessageWithChecks("Let's (try)[https://dev.ppy.sh/home] [https://dev.ppy.sh/b/252238 multiple links] https://dev.ppy.sh/home", 3,
expectedActions: new[] { LinkAction.External, LinkAction.OpenBeatmap, LinkAction.External });
addMessageWithChecks("[https://osu.ppy.sh/home New link format with escaped [and \\[ paired] braces]", 1, expectedActions: LinkAction.External);
addMessageWithChecks("[Markdown link format with escaped [and \\[ paired] braces](https://osu.ppy.sh/home)", 1, expectedActions: LinkAction.External);
addMessageWithChecks("(Old link format with escaped (and \\( paired) parentheses)[https://osu.ppy.sh/home] and [[also a rogue wiki link]]", 2, expectedActions: new[] { LinkAction.External, LinkAction.External });
addMessageWithChecks("[https://dev.ppy.sh/home New link format with escaped [and \\[ paired] braces]", 1, expectedActions: LinkAction.External);
addMessageWithChecks("[Markdown link format with escaped [and \\[ paired] braces](https://dev.ppy.sh/home)", 1, expectedActions: LinkAction.External);
addMessageWithChecks("(Old link format with escaped (and \\( paired) parentheses)[https://dev.ppy.sh/home] and [[also a rogue wiki link]]", 2, expectedActions: new[] { LinkAction.External, LinkAction.External });
// note that there's 0 links here (they get removed if a channel is not found)
addMessageWithChecks("#lobby or #osu would be blue (and work) in the ChatDisplay test (when a proper ChatOverlay is present).");
addMessageWithChecks("I am important!", 0, false, true);
addMessageWithChecks("feels important", 0, true, true);
addMessageWithChecks("likes to post this [https://osu.ppy.sh/home link].", 1, true, true, expectedActions: LinkAction.External);
addMessageWithChecks("likes to post this [https://dev.ppy.sh/home link].", 1, true, true, expectedActions: LinkAction.External);
addMessageWithChecks("Join my multiplayer game osump://12346.", 1, expectedActions: LinkAction.JoinMultiplayerMatch);
addMessageWithChecks("Join my [multiplayer game](osump://12346).", 1, expectedActions: LinkAction.JoinMultiplayerMatch);
addMessageWithChecks("Join my [#english](osu://chan/#english).", 1, expectedActions: LinkAction.OpenChannel);
@ -136,9 +136,9 @@ namespace osu.Game.Tests.Visual.Online
int echoCounter = 0;
addEchoWithWait("sent!", "received!");
addEchoWithWait("https://osu.ppy.sh/home", null, 500);
addEchoWithWait("[https://osu.ppy.sh/forum let's try multiple words too!]");
addEchoWithWait("(long loading times! clickable while loading?)[https://osu.ppy.sh/home]", null, 5000);
addEchoWithWait("https://dev.ppy.sh/home", null, 500);
addEchoWithWait("[https://dev.ppy.sh/forum let's try multiple words too!]");
addEchoWithWait("(long loading times! clickable while loading?)[https://dev.ppy.sh/home]", null, 5000);
void addEchoWithWait(string text, string completeText = null, double delay = 250)
{

View File

@ -41,7 +41,7 @@ namespace osu.Game.Tests.Visual.Online
private class TestFullscreenOverlay : FullscreenOverlay<OverlayHeader>
{
public TestFullscreenOverlay()
: base(OverlayColourScheme.Pink, null)
: base(OverlayColourScheme.Pink)
{
Children = new Drawable[]
{
@ -52,6 +52,17 @@ namespace osu.Game.Tests.Visual.Online
},
};
}
protected override OverlayHeader CreateHeader() => new TestHeader();
internal class TestHeader : OverlayHeader
{
protected override OverlayTitle CreateTitle() => new TestTitle();
internal class TestTitle : OverlayTitle
{
}
}
}
}
}

View File

@ -0,0 +1,33 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Overlays;
using NUnit.Framework;
namespace osu.Game.Tests.Visual.Online
{
[Description("uses online API")]
public class TestSceneOnlineBeatmapListingOverlay : OsuTestScene
{
protected override bool UseOnlineAPI => true;
private readonly BeatmapListingOverlay overlay;
public TestSceneOnlineBeatmapListingOverlay()
{
Add(overlay = new BeatmapListingOverlay());
}
[Test]
public void TestShow()
{
AddStep("Show", overlay.Show);
}
[Test]
public void TestHide()
{
AddStep("Hide", overlay.Hide);
}
}
}

View File

@ -25,7 +25,7 @@ namespace osu.Game.Tests.Visual.Online
Add(rankingsOverlay = new TestRankingsOverlay
{
Country = { BindTarget = countryBindable },
Scope = { BindTarget = scope },
Header = { Current = { BindTarget = scope } },
});
}
@ -65,8 +65,6 @@ namespace osu.Game.Tests.Visual.Online
private class TestRankingsOverlay : RankingsOverlay
{
public new Bindable<Country> Country => base.Country;
public new Bindable<RankingsScope> Scope => base.Scope;
}
}
}

View File

@ -8,16 +8,16 @@ using osu.Game.Users;
using osuTK;
using System;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics.Containers;
using osu.Game.Overlays.Chat;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Online
{
public class TestSceneStandAloneChatDisplay : OsuTestScene
public class TestSceneStandAloneChatDisplay : OsuManualInputManagerTestScene
{
private readonly Channel testChannel = new Channel();
private readonly User admin = new User
{
Username = "HappyStick",
@ -46,92 +46,97 @@ namespace osu.Game.Tests.Visual.Online
[Cached]
private ChannelManager channelManager = new ChannelManager();
private readonly TestStandAloneChatDisplay chatDisplay;
private readonly TestStandAloneChatDisplay chatDisplay2;
private TestStandAloneChatDisplay chatDisplay;
private int messageIdSequence;
private Channel testChannel;
public TestSceneStandAloneChatDisplay()
{
Add(channelManager);
Add(chatDisplay = new TestStandAloneChatDisplay
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Margin = new MarginPadding(20),
Size = new Vector2(400, 80)
});
Add(chatDisplay2 = new TestStandAloneChatDisplay(true)
{
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
Margin = new MarginPadding(20),
Size = new Vector2(400, 150)
});
}
protected override void LoadComplete()
[SetUp]
public void SetUp() => Schedule(() =>
{
base.LoadComplete();
messageIdSequence = 0;
channelManager.CurrentChannel.Value = testChannel = new Channel();
channelManager.CurrentChannel.Value = testChannel;
Children = new[]
{
chatDisplay = new TestStandAloneChatDisplay
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Margin = new MarginPadding(20),
Size = new Vector2(400, 80),
Channel = { Value = testChannel },
},
new TestStandAloneChatDisplay(true)
{
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
Margin = new MarginPadding(20),
Size = new Vector2(400, 150),
Channel = { Value = testChannel },
}
};
});
chatDisplay.Channel.Value = testChannel;
chatDisplay2.Channel.Value = testChannel;
int sequence = 0;
AddStep("message from admin", () => testChannel.AddNewMessages(new Message(sequence++)
[Test]
public void TestManyMessages()
{
AddStep("message from admin", () => testChannel.AddNewMessages(new Message(messageIdSequence++)
{
Sender = admin,
Content = "I am a wang!"
}));
AddStep("message from team red", () => testChannel.AddNewMessages(new Message(sequence++)
AddStep("message from team red", () => testChannel.AddNewMessages(new Message(messageIdSequence++)
{
Sender = redUser,
Content = "I am team red."
}));
AddStep("message from team red", () => testChannel.AddNewMessages(new Message(sequence++)
AddStep("message from team red", () => testChannel.AddNewMessages(new Message(messageIdSequence++)
{
Sender = redUser,
Content = "I plan to win!"
}));
AddStep("message from team blue", () => testChannel.AddNewMessages(new Message(sequence++)
AddStep("message from team blue", () => testChannel.AddNewMessages(new Message(messageIdSequence++)
{
Sender = blueUser,
Content = "Not on my watch. Prepare to eat saaaaaaaaaand. Lots and lots of saaaaaaand."
}));
AddStep("message from admin", () => testChannel.AddNewMessages(new Message(sequence++)
AddStep("message from admin", () => testChannel.AddNewMessages(new Message(messageIdSequence++)
{
Sender = admin,
Content = "Okay okay, calm down guys. Let's do this!"
}));
AddStep("message from long username", () => testChannel.AddNewMessages(new Message(sequence++)
AddStep("message from long username", () => testChannel.AddNewMessages(new Message(messageIdSequence++)
{
Sender = longUsernameUser,
Content = "Hi guys, my new username is lit!"
}));
AddStep("message with new date", () => testChannel.AddNewMessages(new Message(sequence++)
AddStep("message with new date", () => testChannel.AddNewMessages(new Message(messageIdSequence++)
{
Sender = longUsernameUser,
Content = "Message from the future!",
Timestamp = DateTimeOffset.Now
}));
AddUntilStep("ensure still scrolled to bottom", () => chatDisplay.ScrolledToBottom);
checkScrolledToBottom();
const int messages_per_call = 10;
AddRepeatStep("add many messages", () =>
{
for (int i = 0; i < messages_per_call; i++)
{
testChannel.AddNewMessages(new Message(sequence++)
testChannel.AddNewMessages(new Message(messageIdSequence++)
{
Sender = longUsernameUser,
Content = "Many messages! " + Guid.NewGuid(),
@ -153,9 +158,133 @@ namespace osu.Game.Tests.Visual.Online
return true;
});
AddUntilStep("ensure still scrolled to bottom", () => chatDisplay.ScrolledToBottom);
checkScrolledToBottom();
}
/// <summary>
/// Tests that when a message gets wrapped by the chat display getting contracted while scrolled to bottom, the chat will still keep scrolling down.
/// </summary>
[Test]
public void TestMessageWrappingKeepsAutoScrolling()
{
fillChat();
// send message with short words for text wrapping to occur when contracting chat.
sendMessage();
AddStep("contract chat", () => chatDisplay.Width -= 100);
checkScrolledToBottom();
AddStep("send another message", () => testChannel.AddNewMessages(new Message(messageIdSequence++)
{
Sender = admin,
Content = "As we were saying...",
}));
checkScrolledToBottom();
}
[Test]
public void TestUserScrollOverride()
{
fillChat();
sendMessage();
checkScrolledToBottom();
AddStep("User scroll up", () =>
{
InputManager.MoveMouseTo(chatDisplay.ScreenSpaceDrawQuad.Centre);
InputManager.PressButton(MouseButton.Left);
InputManager.MoveMouseTo(chatDisplay.ScreenSpaceDrawQuad.Centre + new Vector2(0, chatDisplay.ScreenSpaceDrawQuad.Height));
InputManager.ReleaseButton(MouseButton.Left);
});
checkNotScrolledToBottom();
sendMessage();
checkNotScrolledToBottom();
AddRepeatStep("User scroll to bottom", () =>
{
InputManager.MoveMouseTo(chatDisplay.ScreenSpaceDrawQuad.Centre);
InputManager.PressButton(MouseButton.Left);
InputManager.MoveMouseTo(chatDisplay.ScreenSpaceDrawQuad.Centre - new Vector2(0, chatDisplay.ScreenSpaceDrawQuad.Height));
InputManager.ReleaseButton(MouseButton.Left);
}, 5);
checkScrolledToBottom();
sendMessage();
checkScrolledToBottom();
}
[Test]
public void TestLocalEchoMessageResetsScroll()
{
fillChat();
sendMessage();
checkScrolledToBottom();
AddStep("User scroll up", () =>
{
InputManager.MoveMouseTo(chatDisplay.ScreenSpaceDrawQuad.Centre);
InputManager.PressButton(MouseButton.Left);
InputManager.MoveMouseTo(chatDisplay.ScreenSpaceDrawQuad.Centre + new Vector2(0, chatDisplay.ScreenSpaceDrawQuad.Height));
InputManager.ReleaseButton(MouseButton.Left);
});
checkNotScrolledToBottom();
sendMessage();
checkNotScrolledToBottom();
sendLocalMessage();
checkScrolledToBottom();
sendMessage();
checkScrolledToBottom();
}
private void fillChat()
{
AddStep("fill chat", () =>
{
for (int i = 0; i < 10; i++)
{
testChannel.AddNewMessages(new Message(messageIdSequence++)
{
Sender = longUsernameUser,
Content = $"some stuff {Guid.NewGuid()}",
});
}
});
checkScrolledToBottom();
}
private void sendMessage()
{
AddStep("send lorem ipsum", () => testChannel.AddNewMessages(new Message(messageIdSequence++)
{
Sender = longUsernameUser,
Content = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce et bibendum velit.",
}));
}
private void sendLocalMessage()
{
AddStep("send local echo", () => testChannel.AddLocalEcho(new LocalEchoMessage
{
Sender = longUsernameUser,
Content = "This is a local echo message.",
}));
}
private void checkScrolledToBottom() =>
AddUntilStep("is scrolled to bottom", () => chatDisplay.ScrolledToBottom);
private void checkNotScrolledToBottom() =>
AddUntilStep("not scrolled to bottom", () => !chatDisplay.ScrolledToBottom);
private class TestStandAloneChatDisplay : StandAloneChatDisplay
{
public TestStandAloneChatDisplay(bool textbox = false)
@ -165,7 +294,7 @@ namespace osu.Game.Tests.Visual.Online
protected DrawableChannel DrawableChannel => InternalChildren.OfType<DrawableChannel>().First();
protected OsuScrollContainer ScrollContainer => (OsuScrollContainer)((Container)DrawableChannel.Child).Child;
protected UserTrackingScrollContainer ScrollContainer => (UserTrackingScrollContainer)((Container)DrawableChannel.Child).Child;
public FillFlowContainer FillFlow => (FillFlowContainer)ScrollContainer.Child;

View File

@ -7,6 +7,8 @@ using osu.Game.Overlays.Comments;
using osu.Game.Online.API.Requests.Responses;
using osu.Framework.Allocation;
using osu.Game.Overlays;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Containers;
namespace osu.Game.Tests.Visual.Online
{
@ -16,13 +18,33 @@ namespace osu.Game.Tests.Visual.Online
[Cached]
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
private VotePill votePill;
[Cached]
private LoginOverlay login;
private TestPill votePill;
private readonly Container pillContainer;
public TestSceneVotePill()
{
AddRange(new Drawable[]
{
pillContainer = new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
AutoSizeAxes = Axes.Both
},
login = new LoginOverlay()
});
}
[Test]
public void TestUserCommentPill()
{
AddStep("Hide login overlay", () => login.Hide());
AddStep("Log in", logIn);
AddStep("User comment", () => addVotePill(getUserComment()));
AddAssert("Background is transparent", () => votePill.Background.Alpha == 0);
AddStep("Click", () => votePill.Click());
AddAssert("Not loading", () => !votePill.IsLoading);
}
@ -30,8 +52,10 @@ namespace osu.Game.Tests.Visual.Online
[Test]
public void TestRandomCommentPill()
{
AddStep("Hide login overlay", () => login.Hide());
AddStep("Log in", logIn);
AddStep("Random comment", () => addVotePill(getRandomComment()));
AddAssert("Background is visible", () => votePill.Background.Alpha == 1);
AddStep("Click", () => votePill.Click());
AddAssert("Loading", () => votePill.IsLoading);
}
@ -39,10 +63,11 @@ namespace osu.Game.Tests.Visual.Online
[Test]
public void TestOfflineRandomCommentPill()
{
AddStep("Hide login overlay", () => login.Hide());
AddStep("Log out", API.Logout);
AddStep("Random comment", () => addVotePill(getRandomComment()));
AddStep("Click", () => votePill.Click());
AddAssert("Not loading", () => !votePill.IsLoading);
AddAssert("Login overlay is visible", () => login.State.Value == Visibility.Visible);
}
private void logIn() => API.Login("localUser", "password");
@ -63,12 +88,22 @@ namespace osu.Game.Tests.Visual.Online
private void addVotePill(Comment comment)
{
Clear();
Add(votePill = new VotePill(comment)
pillContainer.Clear();
pillContainer.Child = votePill = new TestPill(comment)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
});
};
}
private class TestPill : VotePill
{
public new Box Background => base.Background;
public TestPill(Comment comment)
: base(comment)
{
}
}
}
}

View File

@ -4,7 +4,6 @@
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Game.Graphics.Containers;
@ -28,12 +27,7 @@ namespace osu.Game.Tests.Visual.Playlists
{
base.SetUpSteps();
AddStep("push screen", () => LoadScreen(loungeScreen = new PlaylistsLoungeSubScreen
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Width = 0.5f,
}));
AddStep("push screen", () => LoadScreen(loungeScreen = new PlaylistsLoungeSubScreen()));
AddUntilStep("wait for present", () => loungeScreen.IsCurrentScreen());
}

View File

@ -11,12 +11,10 @@ using osu.Framework.Platform;
using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.Rooms;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.OnlinePlay;
using osu.Game.Screens.OnlinePlay.Match.Components;
using osu.Game.Screens.OnlinePlay.Playlists;
using osu.Game.Tests.Beatmaps;
using osu.Game.Users;
@ -85,8 +83,7 @@ namespace osu.Game.Tests.Visual.Playlists
AddStep("move mouse to create button", () =>
{
var footer = match.ChildrenOfType<Footer>().Single();
InputManager.MoveMouseTo(footer.ChildrenOfType<OsuButton>().Single());
InputManager.MoveMouseTo(this.ChildrenOfType<PlaylistsMatchSettingsOverlay.CreateRoomButton>().Single());
});
AddStep("click", () => InputManager.Click(MouseButton.Left));

View File

@ -0,0 +1,21 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.UI;
namespace osu.Game.Tests.Visual.UserInterface
{
public class TestSceneModIcon : OsuTestScene
{
[Test]
public void TestChangeModType()
{
ModIcon icon = null;
AddStep("create mod icon", () => Child = icon = new ModIcon(new OsuModDoubleTime()));
AddStep("change mod", () => icon.Mod = new OsuModEasy());
}
}
}

View File

@ -8,6 +8,7 @@ using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
@ -40,24 +41,8 @@ namespace osu.Game.Tests.Visual.UserInterface
[SetUp]
public void SetUp() => Schedule(() =>
{
Children = new Drawable[]
{
modSelect = new TestModSelectOverlay
{
Origin = Anchor.BottomCentre,
Anchor = Anchor.BottomCentre,
SelectedMods = { BindTarget = SelectedMods }
},
modDisplay = new ModDisplay
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
AutoSizeAxes = Axes.Both,
Position = new Vector2(-5, 25),
Current = { BindTarget = modSelect.SelectedMods }
}
};
SelectedMods.Value = Array.Empty<Mod>();
createDisplay(() => new TestModSelectOverlay());
});
[SetUpSteps]
@ -66,6 +51,50 @@ namespace osu.Game.Tests.Visual.UserInterface
AddStep("show", () => modSelect.Show());
}
[Test]
public void TestSettingsResetOnDeselection()
{
var osuModDoubleTime = new OsuModDoubleTime { SpeedChange = { Value = 1.2 } };
changeRuleset(0);
AddStep("set dt mod with custom rate", () => { SelectedMods.Value = new[] { osuModDoubleTime }; });
AddAssert("selected mod matches", () => (SelectedMods.Value.Single() as OsuModDoubleTime)?.SpeedChange.Value == 1.2);
AddStep("deselect", () => modSelect.DeselectAllButton.Click());
AddAssert("selected mods empty", () => SelectedMods.Value.Count == 0);
AddStep("reselect", () => modSelect.GetModButton(osuModDoubleTime).Click());
AddAssert("selected mod has default value", () => (SelectedMods.Value.Single() as OsuModDoubleTime)?.SpeedChange.IsDefault == true);
}
[Test]
public void TestAnimationFlushOnClose()
{
changeRuleset(0);
AddStep("Select all fun mods", () =>
{
modSelect.ModSectionsContainer
.Single(c => c.ModType == ModType.DifficultyIncrease)
.SelectAll();
});
AddUntilStep("many mods selected", () => modDisplay.Current.Value.Count >= 5);
AddStep("trigger deselect and close overlay", () =>
{
modSelect.ModSectionsContainer
.Single(c => c.ModType == ModType.DifficultyIncrease)
.DeselectAll();
modSelect.Hide();
});
AddAssert("all mods deselected", () => modDisplay.Current.Value.Count == 0);
}
[Test]
public void TestOsuMods()
{
@ -134,6 +163,8 @@ namespace osu.Game.Tests.Visual.UserInterface
[Test]
public void TestExternallySetCustomizedMod()
{
changeRuleset(0);
AddStep("set customized mod externally", () => SelectedMods.Value = new[] { new OsuModDoubleTime { SpeedChange = { Value = 1.01 } } });
AddAssert("ensure button is selected and customized accordingly", () =>
@ -143,6 +174,85 @@ namespace osu.Game.Tests.Visual.UserInterface
});
}
[Test]
public void TestSettingsAreRetainedOnReload()
{
changeRuleset(0);
AddStep("set customized mod externally", () => SelectedMods.Value = new[] { new OsuModDoubleTime { SpeedChange = { Value = 1.01 } } });
AddAssert("setting remains", () => (SelectedMods.Value.SingleOrDefault() as OsuModDoubleTime)?.SpeedChange.Value == 1.01);
AddStep("create overlay", () => createDisplay(() => new TestNonStackedModSelectOverlay()));
AddAssert("setting remains", () => (SelectedMods.Value.SingleOrDefault() as OsuModDoubleTime)?.SpeedChange.Value == 1.01);
}
[Test]
public void TestExternallySetModIsReplacedByOverlayInstance()
{
Mod external = new OsuModDoubleTime();
Mod overlayButtonMod = null;
changeRuleset(0);
AddStep("set mod externally", () => { SelectedMods.Value = new[] { external }; });
AddAssert("ensure button is selected", () =>
{
var button = modSelect.GetModButton(SelectedMods.Value.Single());
overlayButtonMod = button.SelectedMod;
return overlayButtonMod.GetType() == external.GetType();
});
// Right now, when an external change occurs, the ModSelectOverlay will replace the global instance with its own
AddAssert("mod instance doesn't match", () => external != overlayButtonMod);
AddAssert("one mod present in global selected", () => SelectedMods.Value.Count == 1);
AddAssert("globally selected matches button's mod instance", () => SelectedMods.Value.Contains(overlayButtonMod));
AddAssert("globally selected doesn't contain original external change", () => !SelectedMods.Value.Contains(external));
}
[Test]
public void TestNonStacked()
{
changeRuleset(0);
AddStep("create overlay", () => createDisplay(() => new TestNonStackedModSelectOverlay()));
AddStep("show", () => modSelect.Show());
AddAssert("ensure all buttons are spread out", () => modSelect.ChildrenOfType<ModButton>().All(m => m.Mods.Length <= 1));
}
[Test]
public void TestChangeIsValidChangesButtonVisibility()
{
changeRuleset(0);
AddAssert("double time visible", () => modSelect.ChildrenOfType<ModButton>().Any(b => b.Mods.Any(m => m is OsuModDoubleTime)));
AddStep("make double time invalid", () => modSelect.IsValidMod = m => !(m is OsuModDoubleTime));
AddUntilStep("double time not visible", () => modSelect.ChildrenOfType<ModButton>().All(b => !b.Mods.Any(m => m is OsuModDoubleTime)));
AddAssert("nightcore still visible", () => modSelect.ChildrenOfType<ModButton>().Any(b => b.Mods.Any(m => m is OsuModNightcore)));
AddStep("make double time valid again", () => modSelect.IsValidMod = m => true);
AddUntilStep("double time visible", () => modSelect.ChildrenOfType<ModButton>().Any(b => b.Mods.Any(m => m is OsuModDoubleTime)));
AddAssert("nightcore still visible", () => modSelect.ChildrenOfType<ModButton>().Any(b => b.Mods.Any(m => m is OsuModNightcore)));
}
[Test]
public void TestChangeIsValidPreservesSelection()
{
changeRuleset(0);
AddStep("select DT + HD", () => SelectedMods.Value = new Mod[] { new OsuModDoubleTime(), new OsuModHidden() });
AddAssert("DT + HD selected", () => modSelect.ChildrenOfType<ModButton>().Count(b => b.Selected) == 2);
AddStep("make NF invalid", () => modSelect.IsValidMod = m => !(m is ModNoFail));
AddAssert("DT + HD still selected", () => modSelect.ChildrenOfType<ModButton>().Count(b => b.Selected) == 2);
}
private void testSingleMod(Mod mod)
{
selectNext(mod);
@ -262,12 +372,36 @@ namespace osu.Game.Tests.Visual.UserInterface
private void checkLabelColor(Func<Color4> getColour) => AddAssert("check label has expected colour", () => modSelect.MultiplierLabel.Colour.AverageColour == getColour());
private class TestModSelectOverlay : ModSelectOverlay
private void createDisplay(Func<TestModSelectOverlay> createOverlayFunc)
{
Children = new Drawable[]
{
modSelect = createOverlayFunc().With(d =>
{
d.Origin = Anchor.BottomCentre;
d.Anchor = Anchor.BottomCentre;
d.SelectedMods.BindTarget = SelectedMods;
}),
modDisplay = new ModDisplay
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
AutoSizeAxes = Axes.Both,
Position = new Vector2(-5, 25),
Current = { BindTarget = modSelect.SelectedMods }
}
};
}
private class TestModSelectOverlay : LocalPlayerModSelectOverlay
{
public new Bindable<IReadOnlyList<Mod>> SelectedMods => base.SelectedMods;
public bool AllLoaded => ModSectionsContainer.Children.All(c => c.ModIconsLoaded);
public new FillFlowContainer<ModSection> ModSectionsContainer =>
base.ModSectionsContainer;
public ModButton GetModButton(Mod mod)
{
var section = ModSectionsContainer.Children.Single(s => s.ModType == mod.Type);
@ -280,5 +414,10 @@ namespace osu.Game.Tests.Visual.UserInterface
public new Color4 LowMultiplierColour => base.LowMultiplierColour;
public new Color4 HighMultiplierColour => base.HighMultiplierColour;
}
private class TestNonStackedModSelectOverlay : TestModSelectOverlay
{
protected override bool Stacked => false;
}
}
}

View File

@ -151,7 +151,7 @@ namespace osu.Game.Tests.Visual.UserInterface
AddUntilStep("wait for ready", () => modSelect.State.Value == Visibility.Visible && modSelect.ButtonsLoaded);
}
private class TestModSelectOverlay : ModSelectOverlay
private class TestModSelectOverlay : LocalPlayerModSelectOverlay
{
public new VisibilityContainer ModSettingsContainer => base.ModSettingsContainer;
public new TriangleButton CustomiseButton => base.CustomiseButton;

View File

@ -105,6 +105,15 @@ namespace osu.Game.Tests.Visual.UserInterface
checkDisplayedCount(3);
}
[Test]
public void TestError()
{
setState(Visibility.Visible);
AddStep(@"error #1", sendErrorNotification);
AddAssert("Is visible", () => notificationOverlay.State.Value == Visibility.Visible);
checkDisplayedCount(1);
}
[Test]
public void TestSpam()
{
@ -179,7 +188,7 @@ namespace osu.Game.Tests.Visual.UserInterface
private void sendBarrage()
{
switch (RNG.Next(0, 4))
switch (RNG.Next(0, 5))
{
case 0:
sendHelloNotification();
@ -196,6 +205,10 @@ namespace osu.Game.Tests.Visual.UserInterface
case 3:
sendDownloadProgress();
break;
case 4:
sendErrorNotification();
break;
}
}
@ -214,6 +227,11 @@ namespace osu.Game.Tests.Visual.UserInterface
notificationOverlay.Post(new BackgroundNotification { Text = @"Welcome to osu!. Enjoy your stay!" });
}
private void sendErrorNotification()
{
notificationOverlay.Post(new SimpleErrorNotification { Text = @"Rut roh!. Something went wrong!" });
}
private void sendManyNotifications()
{
for (int i = 0; i < 10; i++)

View File

@ -0,0 +1,122 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics.Containers;
using osuTK.Graphics;
namespace osu.Game.Tests.Visual.UserInterface
{
public class TestSceneSectionsContainer : OsuManualInputManagerTestScene
{
private readonly SectionsContainer<TestSection> container;
private float custom;
private const float header_height = 100;
public TestSceneSectionsContainer()
{
container = new SectionsContainer<TestSection>
{
RelativeSizeAxes = Axes.Y,
Width = 300,
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
FixedHeader = new Box
{
Alpha = 0.5f,
Width = 300,
Height = header_height,
Colour = Color4.Red
}
};
container.SelectedSection.ValueChanged += section =>
{
if (section.OldValue != null)
section.OldValue.Selected = false;
if (section.NewValue != null)
section.NewValue.Selected = true;
};
Add(container);
}
[Test]
public void TestSelection()
{
AddStep("clear", () => container.Clear());
AddStep("add 1/8th", () => append(1 / 8.0f));
AddStep("add third", () => append(1 / 3.0f));
AddStep("add half", () => append(1 / 2.0f));
AddStep("add full", () => append(1));
AddSliderStep("set custom", 0.1f, 1.1f, 0.5f, i => custom = i);
AddStep("add custom", () => append(custom));
AddStep("scroll to previous", () => container.ScrollTo(
container.Children.Reverse().SkipWhile(s => s != container.SelectedSection.Value).Skip(1).FirstOrDefault() ?? container.Children.First()
));
AddStep("scroll to next", () => container.ScrollTo(
container.Children.SkipWhile(s => s != container.SelectedSection.Value).Skip(1).FirstOrDefault() ?? container.Children.Last()
));
AddStep("scroll up", () => triggerUserScroll(1));
AddStep("scroll down", () => triggerUserScroll(-1));
}
[Test]
public void TestCorrectSectionSelected()
{
const int sections_count = 11;
float[] alternating = { 0.07f, 0.33f, 0.16f, 0.33f };
AddStep("clear", () => container.Clear());
AddStep("fill with sections", () =>
{
for (int i = 0; i < sections_count; i++)
append(alternating[i % alternating.Length]);
});
void step(int scrollIndex)
{
AddStep($"scroll to section {scrollIndex + 1}", () => container.ScrollTo(container.Children[scrollIndex]));
AddUntilStep("correct section selected", () => container.SelectedSection.Value == container.Children[scrollIndex]);
}
for (int i = 1; i < sections_count; i++)
step(i);
for (int i = sections_count - 2; i >= 0; i--)
step(i);
AddStep("scroll almost to end", () => container.ScrollTo(container.Children[sections_count - 2]));
AddUntilStep("correct section selected", () => container.SelectedSection.Value == container.Children[sections_count - 2]);
AddStep("scroll down", () => triggerUserScroll(-1));
AddUntilStep("correct section selected", () => container.SelectedSection.Value == container.Children[sections_count - 1]);
}
private static readonly ColourInfo selected_colour = ColourInfo.GradientVertical(Color4.Yellow, Color4.Gold);
private static readonly ColourInfo default_colour = ColourInfo.GradientVertical(Color4.White, Color4.DarkGray);
private void append(float multiplier)
{
container.Add(new TestSection
{
Width = 300,
Height = (container.ChildSize.Y - header_height) * multiplier,
Colour = default_colour
});
}
private void triggerUserScroll(float direction)
{
InputManager.MoveMouseTo(container);
InputManager.ScrollVerticalBy(direction);
}
private class TestSection : Box
{
public bool Selected
{
set => Colour = value ? selected_colour : default_colour;
}
}
}
}