Merge branch 'master' into remove-comments-page

This commit is contained in:
smoogipoo
2021-08-13 13:17:13 +09:00
2676 changed files with 127012 additions and 29509 deletions

View File

@ -1,11 +1,16 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
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.Overlays;
using osu.Game.Overlays.AccountCreation;
using osu.Game.Overlays.Settings;
using osu.Game.Users;
namespace osu.Game.Tests.Visual.Online
@ -13,13 +18,12 @@ namespace osu.Game.Tests.Visual.Online
public class TestSceneAccountCreationOverlay : OsuTestScene
{
private readonly Container userPanelArea;
private readonly AccountCreationOverlay accountCreation;
private Bindable<User> localUser;
private IBindable<User> localUser;
public TestSceneAccountCreationOverlay()
{
AccountCreationOverlay accountCreation;
Children = new Drawable[]
{
accountCreation = new AccountCreationOverlay(),
@ -31,19 +35,29 @@ namespace osu.Game.Tests.Visual.Online
Origin = Anchor.TopRight,
},
};
AddStep("show", () => accountCreation.Show());
}
[BackgroundDependencyLoader]
private void load()
{
API.Logout();
localUser = API.LocalUser.GetBoundCopy();
localUser.BindValueChanged(user => { userPanelArea.Child = new UserGridPanel(user.NewValue) { Width = 200 }; }, true);
}
AddStep("logout", API.Logout);
[Test]
public void TestOverlayVisibility()
{
AddStep("start hidden", () => accountCreation.Hide());
AddStep("log out", () => API.Logout());
AddStep("show manually", () => accountCreation.Show());
AddUntilStep("overlay is visible", () => accountCreation.State.Value == Visibility.Visible);
AddStep("click button", () => accountCreation.ChildrenOfType<SettingsButton>().Single().TriggerClick());
AddUntilStep("warning screen is present", () => accountCreation.ChildrenOfType<ScreenWarning>().SingleOrDefault()?.IsPresent == true);
AddStep("log back in", () => API.Login("dummy", "password"));
AddUntilStep("overlay is hidden", () => accountCreation.State.Value == Visibility.Hidden);
}
}
}

View File

@ -1,32 +1,248 @@
// 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;
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;
using osu.Game.Scoring;
using osu.Game.Users;
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()
private BeatmapListingSearchControl searchControl => overlay.ChildrenOfType<BeatmapListingSearchControl>().Single();
[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)) return false;
searchBeatmapSetsRequest.TriggerSuccess(new SearchBeatmapSetsResponse
{
BeatmapSets = setsForResponse,
});
return true;
};
AddStep("initialize dummy", () =>
{
// non-supporter user
((DummyAPIAccess)API).LocalUser.Value = new User
{
Username = "TestBot",
Id = API.LocalUser.Value.Id + 1,
};
});
}
[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()
public void TestUserWithoutSupporterUsesSupporterOnlyFiltersWithoutResults()
{
AddStep("Hide", overlay.Hide);
AddStep("fetch for 0 beatmaps", () => fetchFor());
AddStep("set dummy as non-supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = false);
// only Rank Achieved filter
setRankAchievedFilter(new[] { ScoreRank.XH });
supporterRequiredPlaceholderShown();
setRankAchievedFilter(Array.Empty<ScoreRank>());
notFoundPlaceholderShown();
// only Played filter
setPlayedFilter(SearchPlayed.Played);
supporterRequiredPlaceholderShown();
setPlayedFilter(SearchPlayed.Any);
notFoundPlaceholderShown();
// both RankAchieved and Played filters
setRankAchievedFilter(new[] { ScoreRank.XH });
setPlayedFilter(SearchPlayed.Played);
supporterRequiredPlaceholderShown();
setRankAchievedFilter(Array.Empty<ScoreRank>());
setPlayedFilter(SearchPlayed.Any);
notFoundPlaceholderShown();
}
[Test]
public void TestUserWithSupporterUsesSupporterOnlyFiltersWithoutResults()
{
AddStep("fetch for 0 beatmaps", () => fetchFor());
AddStep("set dummy as supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = true);
// only Rank Achieved filter
setRankAchievedFilter(new[] { ScoreRank.XH });
notFoundPlaceholderShown();
setRankAchievedFilter(Array.Empty<ScoreRank>());
notFoundPlaceholderShown();
// only Played filter
setPlayedFilter(SearchPlayed.Played);
notFoundPlaceholderShown();
setPlayedFilter(SearchPlayed.Any);
notFoundPlaceholderShown();
// both Rank Achieved and Played filters
setRankAchievedFilter(new[] { ScoreRank.XH });
setPlayedFilter(SearchPlayed.Played);
notFoundPlaceholderShown();
setRankAchievedFilter(Array.Empty<ScoreRank>());
setPlayedFilter(SearchPlayed.Any);
notFoundPlaceholderShown();
}
[Test]
public void TestUserWithoutSupporterUsesSupporterOnlyFiltersWithResults()
{
AddStep("fetch for 1 beatmap", () => fetchFor(CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet));
AddStep("set dummy as non-supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = false);
// only Rank Achieved filter
setRankAchievedFilter(new[] { ScoreRank.XH });
supporterRequiredPlaceholderShown();
setRankAchievedFilter(Array.Empty<ScoreRank>());
noPlaceholderShown();
// only Played filter
setPlayedFilter(SearchPlayed.Played);
supporterRequiredPlaceholderShown();
setPlayedFilter(SearchPlayed.Any);
noPlaceholderShown();
// both Rank Achieved and Played filters
setRankAchievedFilter(new[] { ScoreRank.XH });
setPlayedFilter(SearchPlayed.Played);
supporterRequiredPlaceholderShown();
setRankAchievedFilter(Array.Empty<ScoreRank>());
setPlayedFilter(SearchPlayed.Any);
noPlaceholderShown();
}
[Test]
public void TestUserWithSupporterUsesSupporterOnlyFiltersWithResults()
{
AddStep("fetch for 1 beatmap", () => fetchFor(CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet));
AddStep("set dummy as supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = true);
// only Rank Achieved filter
setRankAchievedFilter(new[] { ScoreRank.XH });
noPlaceholderShown();
setRankAchievedFilter(Array.Empty<ScoreRank>());
noPlaceholderShown();
// only Played filter
setPlayedFilter(SearchPlayed.Played);
noPlaceholderShown();
setPlayedFilter(SearchPlayed.Any);
noPlaceholderShown();
// both Rank Achieved and Played filters
setRankAchievedFilter(new[] { ScoreRank.XH });
setPlayedFilter(SearchPlayed.Played);
noPlaceholderShown();
setRankAchievedFilter(Array.Empty<ScoreRank>());
setPlayedFilter(SearchPlayed.Any);
noPlaceholderShown();
}
private void fetchFor(params BeatmapSetInfo[] beatmaps)
{
setsForResponse.Clear();
setsForResponse.AddRange(beatmaps.Select(b => new TestAPIBeatmapSet(b)));
// trigger arbitrary change for fetching.
searchControl.Query.TriggerChange();
}
private void setRankAchievedFilter(ScoreRank[] ranks)
{
AddStep($"set Rank Achieved filter to [{string.Join(',', ranks)}]", () =>
{
searchControl.Ranks.Clear();
searchControl.Ranks.AddRange(ranks);
});
}
private void setPlayedFilter(SearchPlayed played)
{
AddStep($"set Played filter to {played}", () => searchControl.Played.Value = played);
}
private void supporterRequiredPlaceholderShown()
{
AddUntilStep("\"supporter required\" placeholder shown", () => overlay.ChildrenOfType<BeatmapListingOverlay.SupporterRequiredDrawable>().SingleOrDefault()?.IsPresent == true);
}
private void notFoundPlaceholderShown()
{
AddUntilStep("\"no maps found\" placeholder shown", () => overlay.ChildrenOfType<BeatmapListingOverlay.NotFoundDrawable>().SingleOrDefault()?.IsPresent == true);
}
private void noPlaceholderShown()
{
AddUntilStep("no placeholder shown", () =>
!overlay.ChildrenOfType<BeatmapListingOverlay.SupporterRequiredDrawable>().Any()
&& !overlay.ChildrenOfType<BeatmapListingOverlay.NotFoundDrawable>().Any());
}
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,19 @@ 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]
public void TestExplicitBeatmap()
{
AddStep("show explicit map", () =>
{
var beatmapSet = CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet;
beatmapSet.OnlineInfo.HasExplicitContent = true;
overlay.ShowBeatmapSet(beatmapSet);
});
}
[Test]
@ -299,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

@ -7,8 +7,10 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays;
using osu.Game.Overlays.BeatmapSet;
using osu.Game.Screens.Select.Details;
@ -72,6 +74,32 @@ namespace osu.Game.Tests.Visual.Online
};
}
[Test]
public void TestOnlyFailMetrics()
{
AddStep("set beatmap", () => successRate.Beatmap = new BeatmapInfo
{
Metrics = new BeatmapMetrics
{
Fails = Enumerable.Range(1, 100).ToArray(),
}
});
AddAssert("graph max values correct",
() => successRate.ChildrenOfType<BarGraph>().All(graph => graph.MaxValue == 100));
}
[Test]
public void TestEmptyMetrics()
{
AddStep("set beatmap", () => successRate.Beatmap = new BeatmapInfo
{
Metrics = new BeatmapMetrics()
});
AddAssert("graph max values correct",
() => successRate.ChildrenOfType<BarGraph>().All(graph => graph.MaxValue == 0));
}
private class GraphExposingSuccessRate : SuccessRate
{
public new FailRetryGraph Graph => base.Graph;

View File

@ -1,8 +1,14 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using Humanizer;
using NUnit.Framework;
using osu.Framework.Testing;
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.Changelog;
@ -12,13 +18,63 @@ namespace osu.Game.Tests.Visual.Online
[TestFixture]
public class TestSceneChangelogOverlay : OsuTestScene
{
private DummyAPIAccess dummyAPI => (DummyAPIAccess)API;
private readonly Dictionary<string, APIUpdateStream> streams;
private readonly Dictionary<string, APIChangelogBuild> builds;
private APIChangelogBuild requestedBuild;
private TestChangelogOverlay changelog;
protected override bool UseOnlineAPI => true;
public TestSceneChangelogOverlay()
{
streams = APIUpdateStream.KNOWN_STREAMS.Keys.Select((stream, id) => new APIUpdateStream
{
Id = id + 1,
Name = stream,
DisplayName = stream.Humanize(), // not quite there, but good enough.
}).ToDictionary(stream => stream.Name);
string version = DateTimeOffset.Now.ToString("yyyy.Mdd.0");
builds = APIUpdateStream.KNOWN_STREAMS.Keys.Select(stream => new APIChangelogBuild
{
Version = version,
DisplayVersion = version,
UpdateStream = streams[stream],
ChangelogEntries = new List<APIChangelogEntry>()
}).ToDictionary(build => build.UpdateStream.Name);
foreach (var stream in streams.Values)
stream.LatestBuild = builds[stream.Name];
}
[SetUp]
public void SetUp() => Schedule(() =>
{
requestedBuild = null;
dummyAPI.HandleRequest = request =>
{
switch (request)
{
case GetChangelogRequest changelogRequest:
var changelogResponse = new APIChangelogIndex
{
Streams = streams.Values.ToList(),
Builds = builds.Values.ToList()
};
changelogRequest.TriggerSuccess(changelogResponse);
return true;
case GetChangelogBuildRequest buildRequest:
if (requestedBuild != null)
buildRequest.TriggerSuccess(requestedBuild);
return true;
}
return false;
};
Child = changelog = new TestChangelogOverlay();
});
@ -40,65 +96,107 @@ namespace osu.Game.Tests.Visual.Online
AddAssert(@"no stream selected", () => changelog.Header.Streams.Current.Value == null);
}
[Test]
public void ShowWithBuild()
[TestCase(false)]
[TestCase(true)]
public void ShowWithBuild(bool isSupporter)
{
AddStep(@"Show with Lazer 2018.712.0", () =>
AddStep(@"set supporter", () => dummyAPI.LocalUser.Value.IsSupporter = isSupporter);
showBuild(() => new APIChangelogBuild
{
changelog.ShowBuild(new APIChangelogBuild
Version = "2018.712.0",
DisplayVersion = "2018.712.0",
UpdateStream = streams[OsuGameBase.CLIENT_STREAM_NAME],
ChangelogEntries = new List<APIChangelogEntry>
{
Version = "2018.712.0",
DisplayVersion = "2018.712.0",
UpdateStream = new APIUpdateStream { Id = 7, Name = OsuGameBase.CLIENT_STREAM_NAME },
ChangelogEntries = new List<APIChangelogEntry>
new APIChangelogEntry
{
new APIChangelogEntry
Type = ChangelogEntryType.Fix,
Category = "osu!",
Title = "Fix thing",
MessageHtml = "Additional info goes here.",
Repository = "osu",
GithubPullRequestId = 11100,
GithubUser = new APIChangelogUser
{
Category = "Test",
Title = "Title",
MessageHtml = "Message",
OsuUsername = "smoogipoo",
UserId = 1040328
}
},
new APIChangelogEntry
{
Type = ChangelogEntryType.Add,
Category = "osu!",
Title = "Add thing",
Major = true,
Repository = "ppy/osu-framework",
GithubPullRequestId = 4444,
GithubUser = new APIChangelogUser
{
DisplayName = "frenzibyte",
GithubUrl = "https://github.com/frenzibyte"
}
},
new APIChangelogEntry
{
Type = ChangelogEntryType.Misc,
Category = "Code quality",
Title = "Clean up thing",
GithubUser = new APIChangelogUser
{
DisplayName = "some dude"
}
},
new APIChangelogEntry
{
Type = ChangelogEntryType.Misc,
Category = "Code quality",
Title = "Clean up another thing"
}
});
}
});
AddUntilStep(@"wait for streams", () => changelog.Streams?.Count > 0);
AddAssert(@"correct build displayed", () => changelog.Current.Value.Version == "2018.712.0");
AddAssert(@"correct stream selected", () => changelog.Header.Streams.Current.Value.Id == 7);
AddAssert(@"correct stream selected", () => changelog.Header.Streams.Current.Value.Id == 5);
AddUntilStep(@"wait for content load", () => changelog.ChildrenOfType<ChangelogSupporterPromo>().Any());
AddAssert(@"supporter promo showed", () => changelog.ChildrenOfType<ChangelogSupporterPromo>().First().Alpha == (isSupporter ? 0 : 1));
}
[Test]
public void TestHTMLUnescaping()
{
AddStep(@"Ensure HTML string unescaping", () =>
showBuild(() => new APIChangelogBuild
{
changelog.ShowBuild(new APIChangelogBuild
Version = "2019.920.0",
DisplayVersion = "2019.920.0",
UpdateStream = new APIUpdateStream
{
Version = "2019.920.0",
DisplayVersion = "2019.920.0",
UpdateStream = new APIUpdateStream
Name = "Test",
DisplayName = "Test"
},
ChangelogEntries = new List<APIChangelogEntry>
{
new APIChangelogEntry
{
Name = "Test",
DisplayName = "Test"
},
ChangelogEntries = new List<APIChangelogEntry>
{
new APIChangelogEntry
Category = "Testing HTML strings unescaping",
Title = "Ensuring HTML strings are being unescaped",
MessageHtml = "&quot;&quot;&quot;This text should appear triple-quoted&quot;&quot;&quot; &gt;_&lt;",
GithubUser = new APIChangelogUser
{
Category = "Testing HTML strings unescaping",
Title = "Ensuring HTML strings are being unescaped",
MessageHtml = "&quot;&quot;&quot;This text should appear triple-quoted&quot;&quot;&quot; &gt;_&lt;",
GithubUser = new APIChangelogUser
{
DisplayName = "Dummy",
OsuUsername = "Dummy",
}
},
}
});
DisplayName = "Dummy",
OsuUsername = "Dummy",
}
},
}
});
}
private void showBuild(Func<APIChangelogBuild> build)
{
AddStep("set up build", () => requestedBuild = build.Invoke());
AddStep("show build", () => changelog.ShowBuild(requestedBuild));
}
private class TestChangelogOverlay : ChangelogOverlay
{
public new List<APIUpdateStream> Streams => base.Streams;

View File

@ -0,0 +1,35 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Overlays;
using osu.Game.Overlays.Changelog;
namespace osu.Game.Tests.Visual.Online
{
public class TestSceneChangelogSupporterPromo : OsuTestScene
{
[Cached]
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
public TestSceneChangelogSupporterPromo()
{
Child = new Container
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = colourProvider.Background4,
},
new ChangelogSupporterPromo(),
}
};
}
}
}

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

@ -9,8 +9,12 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input;
using osu.Framework.Platform;
using osu.Framework.Testing;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Online.Chat;
using osu.Game.Overlays;
using osu.Game.Overlays.Chat.Selection;
@ -34,6 +38,10 @@ namespace osu.Game.Tests.Visual.Online
private Channel previousChannel => joinedChannels.ElementAt(joinedChannels.ToList().IndexOf(currentChannel) - 1);
private Channel channel1 => channels[0];
private Channel channel2 => channels[1];
private Channel channel3 => channels[2];
[Resolved]
private GameHost host { get; set; }
public TestSceneChatOverlay()
{
@ -42,7 +50,8 @@ namespace osu.Game.Tests.Visual.Online
{
Name = $"Channel no. {index}",
Topic = index == 3 ? null : $"We talk about the number {index} here",
Type = index % 2 == 0 ? ChannelType.PM : ChannelType.Temporary
Type = index % 2 == 0 ? ChannelType.PM : ChannelType.Temporary,
Id = index
})
.ToList();
}
@ -64,6 +73,25 @@ namespace osu.Game.Tests.Visual.Online
});
}
[SetUpSteps]
public void SetUpSteps()
{
AddStep("register request handling", () =>
{
((DummyAPIAccess)API).HandleRequest = req =>
{
switch (req)
{
case JoinChannelRequest joinChannel:
joinChannel.TriggerSuccess();
return true;
}
return false;
};
});
}
[Test]
public void TestHideOverlay()
{
@ -103,11 +131,7 @@ namespace osu.Game.Tests.Visual.Online
public void TestChannelShortcutKeys()
{
AddStep("Join channels", () => channels.ForEach(channel => channelManager.JoinChannel(channel)));
AddStep("Close channel selector", () =>
{
InputManager.PressKey(Key.Escape);
InputManager.ReleaseKey(Key.Escape);
});
AddStep("Close channel selector", () => InputManager.Key(Key.Escape));
AddUntilStep("Wait for close", () => chatOverlay.SelectionOverlayState == Visibility.Hidden);
for (int zeroBasedIndex = 0; zeroBasedIndex < 10; ++zeroBasedIndex)
@ -212,15 +236,106 @@ namespace osu.Game.Tests.Visual.Online
AddAssert("All channels closed", () => !channelManager.JoinedChannels.Any());
}
[Test]
public void TestCloseTabShortcut()
{
AddStep("Join 2 channels", () =>
{
channelManager.JoinChannel(channel1);
channelManager.JoinChannel(channel2);
});
// Want to close channel 2
AddStep("Select channel 2", () => clickDrawable(chatOverlay.TabMap[channel2]));
AddStep("Close tab via shortcut", pressCloseDocumentKeys);
// Channel 2 should be closed
AddAssert("Channel 1 open", () => channelManager.JoinedChannels.Contains(channel1));
AddAssert("Channel 2 closed", () => !channelManager.JoinedChannels.Contains(channel2));
// Want to close channel 1
AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1]));
AddStep("Close tab via shortcut", pressCloseDocumentKeys);
// Channel 1 and channel 2 should be closed
AddAssert("All channels closed", () => !channelManager.JoinedChannels.Any());
}
[Test]
public void TestNewTabShortcut()
{
AddStep("Join 2 channels", () =>
{
channelManager.JoinChannel(channel1);
channelManager.JoinChannel(channel2);
});
// Want to join another channel
AddStep("Press new tab shortcut", pressNewTabKeys);
// Selector should be visible
AddAssert("Selector is visible", () => chatOverlay.SelectionOverlayState == Visibility.Visible);
}
[Test]
public void TestRestoreTabShortcut()
{
AddStep("Join 3 channels", () =>
{
channelManager.JoinChannel(channel1);
channelManager.JoinChannel(channel2);
channelManager.JoinChannel(channel3);
});
// Should do nothing
AddStep("Restore tab via shortcut", pressRestoreTabKeys);
AddAssert("All channels still open", () => channelManager.JoinedChannels.Count == 3);
// Close channel 1
AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1]));
AddStep("Click normal close button", () => clickDrawable(((TestChannelTabItem)chatOverlay.TabMap[channel1]).CloseButton.Child));
AddAssert("Channel 1 closed", () => !channelManager.JoinedChannels.Contains(channel1));
AddAssert("Other channels still open", () => channelManager.JoinedChannels.Count == 2);
// Reopen channel 1
AddStep("Restore tab via shortcut", pressRestoreTabKeys);
AddAssert("All channels now open", () => channelManager.JoinedChannels.Count == 3);
AddAssert("Current channel is channel 1", () => currentChannel == channel1);
// Close two channels
AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1]));
AddStep("Close channel 1", () => clickDrawable(((TestChannelTabItem)chatOverlay.TabMap[channel1]).CloseButton.Child));
AddStep("Select channel 2", () => clickDrawable(chatOverlay.TabMap[channel2]));
AddStep("Close channel 2", () => clickDrawable(((TestPrivateChannelTabItem)chatOverlay.TabMap[channel2]).CloseButton.Child));
AddAssert("Only one channel open", () => channelManager.JoinedChannels.Count == 1);
AddAssert("Current channel is channel 3", () => currentChannel == channel3);
// Should first re-open channel 2
AddStep("Restore tab via shortcut", pressRestoreTabKeys);
AddAssert("Channel 1 still closed", () => !channelManager.JoinedChannels.Contains(channel1));
AddAssert("Channel 2 now open", () => channelManager.JoinedChannels.Contains(channel2));
AddAssert("Current channel is channel 2", () => currentChannel == channel2);
// Should then re-open channel 1
AddStep("Restore tab via shortcut", pressRestoreTabKeys);
AddAssert("All channels now open", () => channelManager.JoinedChannels.Count == 3);
AddAssert("Current channel is channel 1", () => currentChannel == channel1);
}
private void pressChannelHotkey(int number)
{
var channelKey = Key.Number0 + number;
InputManager.PressKey(Key.AltLeft);
InputManager.PressKey(channelKey);
InputManager.Key(channelKey);
InputManager.ReleaseKey(Key.AltLeft);
InputManager.ReleaseKey(channelKey);
}
private void pressCloseDocumentKeys() => InputManager.Keys(PlatformAction.DocumentClose);
private void pressNewTabKeys() => InputManager.Keys(PlatformAction.TabNew);
private void pressRestoreTabKeys() => InputManager.Keys(PlatformAction.TabRestore);
private void clickDrawable(Drawable d)
{
InputManager.MoveMouseTo(d);

View File

@ -68,7 +68,7 @@ namespace osu.Game.Tests.Visual.Online
public void TestMultipleLoads()
{
var comments = exampleComments;
int topLevelCommentCount = exampleComments.Comments.Count(comment => comment.IsTopLevel);
int topLevelCommentCount = exampleComments.Comments.Count;
AddStep("hide container", () => commentsContainer.Hide());
setUpCommentsResponse(comments);
@ -85,9 +85,10 @@ namespace osu.Game.Tests.Visual.Online
dummyAPI.HandleRequest = request =>
{
if (!(request is GetCommentsRequest getCommentsRequest))
return;
return false;
getCommentsRequest.TriggerSuccess(commentBundle);
return true;
};
});

View File

@ -0,0 +1,100 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Game.Database;
using osu.Game.Online.Spectator;
using osu.Game.Overlays.Dashboard;
using osu.Game.Tests.Visual.Spectator;
using osu.Game.Users;
namespace osu.Game.Tests.Visual.Online
{
public class TestSceneCurrentlyPlayingDisplay : OsuTestScene
{
private readonly User streamingUser = new User { Id = 2, Username = "Test user" };
private TestSpectatorClient spectatorClient;
private CurrentlyPlayingDisplay currentlyPlaying;
[SetUpSteps]
public void SetUpSteps()
{
AddStep("add streaming client", () =>
{
spectatorClient = new TestSpectatorClient();
var lookupCache = new TestUserLookupCache();
Children = new Drawable[]
{
lookupCache,
spectatorClient,
new DependencyProvidingContainer
{
RelativeSizeAxes = Axes.Both,
CachedDependencies = new (Type, object)[]
{
(typeof(SpectatorClient), spectatorClient),
(typeof(UserLookupCache), lookupCache)
},
Child = currentlyPlaying = new CurrentlyPlayingDisplay
{
RelativeSizeAxes = Axes.Both,
}
},
};
});
}
[Test]
public void TestBasicDisplay()
{
AddStep("Add playing user", () => spectatorClient.StartPlay(streamingUser.Id, 0));
AddUntilStep("Panel loaded", () => currentlyPlaying.ChildrenOfType<UserGridPanel>()?.FirstOrDefault()?.User.Id == 2);
AddStep("Remove playing user", () => spectatorClient.EndPlay(streamingUser.Id));
AddUntilStep("Panel no longer present", () => !currentlyPlaying.ChildrenOfType<UserGridPanel>().Any());
}
internal class TestUserLookupCache : UserLookupCache
{
private static readonly string[] usernames =
{
"fieryrage",
"Kerensa",
"MillhioreF",
"Player01",
"smoogipoo",
"Ephemeral",
"BTMC",
"Cilvery",
"m980",
"HappyStick",
"LittleEndu",
"frenzibyte",
"Zallius",
"BanchoBot",
"rocketminer210",
"pishifat"
};
protected override Task<User> ComputeValueAsync(int lookup, CancellationToken token = default)
{
// tests against failed lookups
if (lookup == 13)
return Task.FromResult<User>(null);
return Task.FromResult(new User
{
Id = lookup,
Username = usernames[lookup % usernames.Length],
});
}
}
}
}

View File

@ -42,8 +42,11 @@ namespace osu.Game.Tests.Visual.Online
ensureSoleilyRemoved();
createButtonWithBeatmap(createSoleily());
AddAssert("button state not downloaded", () => downloadButton.DownloadState == DownloadState.NotDownloaded);
AddStep("import soleily", () => beatmaps.Import(new[] { TestResources.GetTestBeatmapForImport() }));
AddStep("import soleily", () => beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()));
AddUntilStep("wait for beatmap import", () => beatmaps.GetAllUsableBeatmapSets().Any(b => b.OnlineBeatmapSetID == 241526));
AddAssert("button state downloaded", () => downloadButton.DownloadState == DownloadState.LocallyAvailable);
createButtonWithBeatmap(createSoleily());
AddAssert("button state downloaded", () => downloadButton.DownloadState == DownloadState.LocallyAvailable);
ensureSoleilyRemoved();

View File

@ -99,13 +99,16 @@ namespace osu.Game.Tests.Visual.Online
[BackgroundDependencyLoader]
private void load(RulesetStore rulesets)
{
var normal = CreateWorkingBeatmap(Ruleset.Value).BeatmapSetInfo;
var normal = CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet;
normal.OnlineInfo.HasVideo = true;
normal.OnlineInfo.HasStoryboard = true;
var undownloadable = getUndownloadableBeatmapSet();
var manyDifficulties = getManyDifficultiesBeatmapSet(rulesets);
var explicitMap = CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet;
explicitMap.OnlineInfo.HasExplicitContent = true;
Child = new BasicScrollContainer
{
RelativeSizeAxes = Axes.Both,
@ -121,9 +124,11 @@ namespace osu.Game.Tests.Visual.Online
new GridBeatmapPanel(normal),
new GridBeatmapPanel(undownloadable),
new GridBeatmapPanel(manyDifficulties),
new GridBeatmapPanel(explicitMap),
new ListBeatmapPanel(normal),
new ListBeatmapPanel(undownloadable),
new ListBeatmapPanel(manyDifficulties),
new ListBeatmapPanel(explicitMap)
},
},
};

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.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays;
using osu.Game.Overlays.Comments;
namespace osu.Game.Tests.Visual.Online
{
public class TestSceneDrawableComment : OsuTestScene
{
[Cached]
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
private Container container;
[SetUp]
public void SetUp() => Schedule(() =>
{
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = colourProvider.Background4,
},
container = new Container
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
},
};
});
[TestCaseSource(nameof(comments))]
public void TestComment(string description, string text)
{
AddStep(description, () =>
{
comment.Message = text;
container.Add(new DrawableComment(comment));
});
}
private static readonly Comment comment = new Comment
{
Id = 1,
LegacyName = "Test User",
CreatedAt = DateTimeOffset.Now,
VotesCount = 0,
};
private static object[] comments =
{
new[] { "Plain", "This is plain comment" },
new[] { "Link", "Please visit https://osu.ppy.sh" },
new[]
{
"Heading", @"# Heading 1
## Heading 2
### Heading 3
#### Heading 4
##### Heading 5
###### Heading 6"
},
// Taken from https://github.com/ppy/osu/issues/13993#issuecomment-885994077
new[]
{
"Problematic", @"My tablet doesn't work :(
It's a Huion 420 and it's apparently incompatible with OpenTablet Driver. The warning I get is: ""DeviceInUseException: Device is currently in use by another kernel module. To fix this issue, please follow the instructions from https://github.com/OpenTabletDriver/OpenTabletDriver/wiki/Linux-FAQ#arg umentoutofrangeexception-value-0-15"" and it repeats 4 times on the notification before logging subsequent warnings.
Checking the logs, it looks for other Huion tablets before sending the notification (e.g.
""2021-07-23 03:52:33 [verbose]: Detect: Searching for tablet 'Huion WH1409 V2'
20 2021-07-23 03:52:33 [error]: DeviceInUseException: Device is currently in use by another kernel module. To fix this issue, please follow the instructions from https://github.com/OpenTabletDriver/OpenTabletDriver/wiki/Linux-FAQ#arg umentoutofrangeexception-value-0-15"")
I use an Arch based installation of Linux and the tablet runs perfectly with Digimend kernel driver, with area configuration, pen pressure, etc. On osu!lazer the cursor disappears until I set it to ""Borderless"" instead of ""Fullscreen"" and even after it shows up, it goes to the bottom left corner as soon as a map starts.
I have honestly 0 idea of whats going on at this point."
}
};
}
}

View File

@ -3,14 +3,13 @@
using System;
using System.Collections.Generic;
using osu.Framework.Graphics.Containers;
using osu.Game.Overlays.Dashboard.Friends;
using osu.Framework.Graphics;
using osu.Game.Users;
using osu.Game.Overlays;
using osu.Framework.Allocation;
using NUnit.Framework;
using osu.Game.Online.API;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Overlays;
using osu.Game.Overlays.Dashboard.Friends;
using osu.Game.Users;
namespace osu.Game.Tests.Visual.Online
{
@ -21,7 +20,7 @@ namespace osu.Game.Tests.Visual.Online
[Cached]
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
private TestFriendDisplay display;
private FriendDisplay display;
[SetUp]
public void Setup() => Schedule(() =>
@ -29,20 +28,20 @@ namespace osu.Game.Tests.Visual.Online
Child = new BasicScrollContainer
{
RelativeSizeAxes = Axes.Both,
Child = display = new TestFriendDisplay()
Child = display = new FriendDisplay()
};
});
[Test]
public void TestOffline()
{
AddStep("Populate", () => display.Users = getUsers());
AddStep("Populate with offline test users", () => display.Users = getUsers());
}
[Test]
public void TestOnline()
{
AddStep("Fetch online", () => display?.Fetch());
// No need to do anything, fetch is performed automatically.
}
private List<User> getUsers() => new List<User>
@ -52,7 +51,7 @@ namespace osu.Game.Tests.Visual.Online
Username = "flyte",
Id = 3103765,
IsOnline = true,
CurrentModeRank = 1111,
Statistics = new UserStatistics { GlobalRank = 1111 },
Country = new Country { FlagName = "JP" },
CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c6.jpg"
},
@ -61,7 +60,7 @@ namespace osu.Game.Tests.Visual.Online
Username = "peppy",
Id = 2,
IsOnline = false,
CurrentModeRank = 2222,
Statistics = new UserStatistics { GlobalRank = 2222 },
Country = new Country { FlagName = "AU" },
CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
IsSupporter = true,
@ -77,17 +76,5 @@ namespace osu.Game.Tests.Visual.Online
LastVisit = DateTimeOffset.Now
}
};
private class TestFriendDisplay : FriendDisplay
{
public void Fetch()
{
base.APIStateChanged(API, APIState.Online);
}
public override void APIStateChanged(IAPIProvider api, APIState state)
{
}
}
}
}

View File

@ -12,7 +12,7 @@ namespace osu.Game.Tests.Visual.Online
[TestFixture]
public class TestSceneFullscreenOverlay : OsuTestScene
{
private FullscreenOverlay overlay;
private FullscreenOverlay<OverlayHeader> overlay;
protected override void LoadComplete()
{
@ -38,7 +38,7 @@ namespace osu.Game.Tests.Visual.Online
AddAssert("fire count 3", () => fireCount == 3);
}
private class TestFullscreenOverlay : FullscreenOverlay
private class TestFullscreenOverlay : FullscreenOverlay<OverlayHeader>
{
public TestFullscreenOverlay()
: base(OverlayColourScheme.Pink)
@ -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,61 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics;
using osu.Game.Online.API.Requests.Responses;
using osu.Framework.Allocation;
using osu.Game.Overlays;
using System;
using osu.Game.Overlays.Dashboard.Home.News;
using osuTK;
using System.Collections.Generic;
namespace osu.Game.Tests.Visual.Online
{
public class TestSceneHomeNewsPanel : OsuTestScene
{
[Cached]
private readonly OverlayColourProvider overlayColour = new OverlayColourProvider(OverlayColourScheme.Purple);
public TestSceneHomeNewsPanel()
{
Add(new FillFlowContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
AutoSizeAxes = Axes.Y,
Width = 500,
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 5),
Children = new Drawable[]
{
new FeaturedNewsItemPanel(new APINewsPost
{
Title = "This post has an image which starts with \"/\" and has many authors!",
Preview = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
FirstImage = "/help/wiki/shared/news/banners/monthly-beatmapping-contest.png",
PublishedAt = DateTimeOffset.Now,
Slug = "2020-07-16-summer-theme-park-2020-voting-open"
}),
new NewsItemGroupPanel(new List<APINewsPost>
{
new APINewsPost
{
Title = "Title 1",
Slug = "2020-07-16-summer-theme-park-2020-voting-open",
PublishedAt = DateTimeOffset.Now,
},
new APINewsPost
{
Title = "Title of this post is Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
Slug = "2020-07-16-summer-theme-park-2020-voting-open",
PublishedAt = DateTimeOffset.Now,
}
}),
new ShowMoreNewsPanel()
}
});
}
}
}

View File

@ -13,6 +13,7 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Bindables;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Game.Graphics.Sprites;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
@ -45,14 +46,14 @@ namespace osu.Game.Tests.Visual.Online
switch (args.Action)
{
case NotifyCollectionChangedAction.Add:
args.NewItems.Cast<Mod>().ForEach(mod => selectedMods.Add(new OsuSpriteText
args.NewItems.AsNonNull().Cast<Mod>().ForEach(mod => selectedMods.Add(new OsuSpriteText
{
Text = mod.Acronym,
}));
break;
case NotifyCollectionChangedAction.Remove:
args.OldItems.Cast<Mod>().ForEach(mod =>
args.OldItems.AsNonNull().Cast<Mod>().ForEach(mod =>
{
foreach (var selected in selectedMods)
{

View File

@ -0,0 +1,240 @@
// 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.Graphics.Containers;
using osu.Framework.Testing;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Chat;
using osu.Game.Overlays;
using osu.Game.Overlays.Notifications;
using osu.Game.Users;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Online
{
public class TestSceneMessageNotifier : OsuManualInputManagerTestScene
{
private User friend;
private Channel publicChannel;
private Channel privateMessageChannel;
private TestContainer testContainer;
private int messageIdCounter;
[SetUp]
public void Setup()
{
if (API is DummyAPIAccess daa)
{
daa.HandleRequest = dummyAPIHandleRequest;
}
friend = new User { Id = 0, Username = "Friend" };
publicChannel = new Channel { Id = 1, Name = "osu" };
privateMessageChannel = new Channel(friend) { Id = 2, Name = friend.Username, Type = ChannelType.PM };
Schedule(() =>
{
Child = testContainer = new TestContainer(new[] { publicChannel, privateMessageChannel })
{
RelativeSizeAxes = Axes.Both,
};
testContainer.ChatOverlay.Show();
});
}
private bool dummyAPIHandleRequest(APIRequest request)
{
switch (request)
{
case GetMessagesRequest messagesRequest:
messagesRequest.TriggerSuccess(new List<Message>(0));
return true;
case CreateChannelRequest createChannelRequest:
var apiChatChannel = new APIChatChannel
{
RecentMessages = new List<Message>(0),
ChannelID = (int)createChannelRequest.Channel.Id
};
createChannelRequest.TriggerSuccess(apiChatChannel);
return true;
case ListChannelsRequest listChannelsRequest:
listChannelsRequest.TriggerSuccess(new List<Channel>(1) { publicChannel });
return true;
case GetUpdatesRequest updatesRequest:
updatesRequest.TriggerSuccess(new GetUpdatesResponse
{
Messages = new List<Message>(0),
Presence = new List<Channel>(0)
});
return true;
case JoinChannelRequest joinChannelRequest:
joinChannelRequest.TriggerSuccess();
return true;
default:
return false;
}
}
[Test]
public void TestPublicChannelMention()
{
AddStep("switch to PMs", () => testContainer.ChannelManager.CurrentChannel.Value = privateMessageChannel);
AddStep("receive public message", () => receiveMessage(friend, publicChannel, "Hello everyone"));
AddAssert("no notifications fired", () => testContainer.NotificationOverlay.UnreadCount.Value == 0);
AddStep("receive message containing mention", () => receiveMessage(friend, publicChannel, $"Hello {API.LocalUser.Value.Username.ToLowerInvariant()}!"));
AddAssert("1 notification fired", () => testContainer.NotificationOverlay.UnreadCount.Value == 1);
AddStep("open notification overlay", () => testContainer.NotificationOverlay.Show());
AddStep("click notification", clickNotification<MessageNotifier.MentionNotification>);
AddAssert("chat overlay is open", () => testContainer.ChatOverlay.State.Value == Visibility.Visible);
AddAssert("public channel is selected", () => testContainer.ChannelManager.CurrentChannel.Value == publicChannel);
}
[Test]
public void TestPrivateMessageNotification()
{
AddStep("switch to public channel", () => testContainer.ChannelManager.CurrentChannel.Value = publicChannel);
AddStep("receive PM", () => receiveMessage(friend, privateMessageChannel, $"Hello {API.LocalUser.Value.Username}"));
AddAssert("1 notification fired", () => testContainer.NotificationOverlay.UnreadCount.Value == 1);
AddStep("open notification overlay", () => testContainer.NotificationOverlay.Show());
AddStep("click notification", clickNotification<MessageNotifier.PrivateMessageNotification>);
AddAssert("chat overlay is open", () => testContainer.ChatOverlay.State.Value == Visibility.Visible);
AddAssert("PM channel is selected", () => testContainer.ChannelManager.CurrentChannel.Value == privateMessageChannel);
}
[Test]
public void TestNoNotificationWhenPMChannelOpen()
{
AddStep("switch to PMs", () => testContainer.ChannelManager.CurrentChannel.Value = privateMessageChannel);
AddStep("receive PM", () => receiveMessage(friend, privateMessageChannel, "you're reading this, right?"));
AddAssert("no notifications fired", () => testContainer.NotificationOverlay.UnreadCount.Value == 0);
}
[Test]
public void TestNoNotificationWhenMentionedInOpenPublicChannel()
{
AddStep("switch to public channel", () => testContainer.ChannelManager.CurrentChannel.Value = publicChannel);
AddStep("receive mention", () => receiveMessage(friend, publicChannel, $"{API.LocalUser.Value.Username.ToUpperInvariant()} has been reading this"));
AddAssert("no notifications fired", () => testContainer.NotificationOverlay.UnreadCount.Value == 0);
}
[Test]
public void TestNoNotificationOnSelfMention()
{
AddStep("switch to PM channel", () => testContainer.ChannelManager.CurrentChannel.Value = privateMessageChannel);
AddStep("receive self-mention", () => receiveMessage(API.LocalUser.Value, publicChannel, $"my name is {API.LocalUser.Value.Username}"));
AddAssert("no notifications fired", () => testContainer.NotificationOverlay.UnreadCount.Value == 0);
}
[Test]
public void TestNoNotificationOnPMFromSelf()
{
AddStep("switch to public channel", () => testContainer.ChannelManager.CurrentChannel.Value = publicChannel);
AddStep("receive PM from self", () => receiveMessage(API.LocalUser.Value, privateMessageChannel, "hey hey"));
AddAssert("no notifications fired", () => testContainer.NotificationOverlay.UnreadCount.Value == 0);
}
[Test]
public void TestNotificationsNotFiredTwice()
{
AddStep("switch to public channel", () => testContainer.ChannelManager.CurrentChannel.Value = publicChannel);
AddStep("receive same PM twice", () =>
{
var message = createMessage(friend, privateMessageChannel, "hey hey");
privateMessageChannel.AddNewMessages(message, message);
});
AddStep("open notification overlay", () => testContainer.NotificationOverlay.Show());
AddAssert("1 notification fired", () => testContainer.NotificationOverlay.UnreadCount.Value == 1);
}
private void receiveMessage(User sender, Channel channel, string content) => channel.AddNewMessages(createMessage(sender, channel, content));
private Message createMessage(User sender, Channel channel, string content) => new Message(messageIdCounter++)
{
Content = content,
Sender = sender,
ChannelId = channel.Id
};
private void clickNotification<T>() where T : Notification
{
var notification = testContainer.NotificationOverlay.ChildrenOfType<T>().Single();
InputManager.MoveMouseTo(notification);
InputManager.Click(MouseButton.Left);
}
private class TestContainer : Container
{
[Cached]
public ChannelManager ChannelManager { get; } = new ChannelManager();
[Cached]
public NotificationOverlay NotificationOverlay { get; } = new NotificationOverlay
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
};
[Cached]
public ChatOverlay ChatOverlay { get; } = new ChatOverlay();
private readonly MessageNotifier messageNotifier = new MessageNotifier();
private readonly Channel[] channels;
public TestContainer(Channel[] channels)
{
this.channels = channels;
}
[BackgroundDependencyLoader]
private void load()
{
Children = new Drawable[]
{
ChannelManager,
ChatOverlay,
NotificationOverlay,
messageNotifier,
};
((BindableList<Channel>)ChannelManager.AvailableChannels).AddRange(channels);
foreach (var channel in channels)
ChannelManager.JoinChannel(channel);
}
}
}
}

View File

@ -31,15 +31,16 @@ namespace osu.Game.Tests.Visual.Online
{
new NewsCard(new APINewsPost
{
Title = "This post has an image which starts with \"/\" and has many authors!",
Title = "This post has an image which starts with \"/\" and has many authors! (clickable)",
Preview = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
Author = "someone, someone1, someone2, someone3, someone4",
FirstImage = "/help/wiki/shared/news/banners/monthly-beatmapping-contest.png",
PublishedAt = DateTimeOffset.Now
PublishedAt = DateTimeOffset.Now,
Slug = "2020-07-16-summer-theme-park-2020-voting-open"
}),
new NewsCard(new APINewsPost
{
Title = "This post has a full-url image! (HTML entity: &amp;)",
Title = "This post has a full-url image! (HTML entity: &amp;) (non-clickable)",
Preview = "boom (HTML entity: &amp;)",
Author = "user (HTML entity: &amp;)",
FirstImage = "https://assets.ppy.sh/artists/88/header.jpg",

View File

@ -0,0 +1,53 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
using osu.Game.Overlays.News;
using osu.Framework.Graphics;
using osu.Game.Overlays;
using osu.Framework.Allocation;
namespace osu.Game.Tests.Visual.Online
{
public class TestSceneNewsHeader : OsuTestScene
{
[Cached]
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
private TestHeader header;
[SetUp]
public void Setup() => Schedule(() =>
{
Child = header = new TestHeader
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre
};
});
[Test]
public void TestControl()
{
AddAssert("Front page selected", () => header.Current.Value == NewsHeader.FrontPageString);
AddAssert("1 tab total", () => header.TabCount == 1);
AddStep("Set article 1", () => header.SetArticle("1"));
AddAssert("Article 1 selected", () => header.Current.Value == "1");
AddAssert("2 tabs total", () => header.TabCount == 2);
AddStep("Set article 2", () => header.SetArticle("2"));
AddAssert("Article 2 selected", () => header.Current.Value == "2");
AddAssert("2 tabs total", () => header.TabCount == 2);
AddStep("Set front page", () => header.SetFrontPage());
AddAssert("Front page selected", () => header.Current.Value == NewsHeader.FrontPageString);
AddAssert("1 tab total", () => header.TabCount == 1);
}
private class TestHeader : NewsHeader
{
public int TabCount => TabControl.Items.Count;
}
}
}

View File

@ -2,65 +2,113 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using osu.Framework.Graphics;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Testing;
using osu.Game.Graphics.UserInterface;
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.News;
namespace osu.Game.Tests.Visual.Online
{
public class TestSceneNewsOverlay : OsuTestScene
{
private TestNewsOverlay news;
private DummyAPIAccess dummyAPI => (DummyAPIAccess)API;
protected override void LoadComplete()
private NewsOverlay overlay;
[SetUp]
public void SetUp() => Schedule(() => Child = overlay = new NewsOverlay());
[Test]
public void TestRequest()
{
base.LoadComplete();
Add(news = new TestNewsOverlay());
AddStep(@"Show", news.Show);
AddStep(@"Hide", news.Hide);
AddStep(@"Show front page", () => news.ShowFrontPage());
AddStep(@"Custom article", () => news.Current.Value = "Test Article 101");
AddStep(@"Article covers", () => news.LoadAndShowContent(new NewsCoverTest()));
setUpNewsResponse(responseExample);
AddStep("Show", () => overlay.Show());
AddStep("Show article", () => overlay.ShowArticle("article"));
}
private class TestNewsOverlay : NewsOverlay
[Test]
public void TestCursorRequest()
{
public new void LoadAndShowContent(NewsContent content) => base.LoadAndShowContent(content);
setUpNewsResponse(responseWithCursor, "Set up cursor response");
AddStep("Show", () => overlay.Show());
AddUntilStep("Show More button is visible", () => showMoreButton?.Alpha == 1);
setUpNewsResponse(responseWithNoCursor, "Set up no cursor response");
AddStep("Click Show More", () => showMoreButton?.TriggerClick());
AddUntilStep("Show More button is hidden", () => showMoreButton?.Alpha == 0);
}
private class NewsCoverTest : NewsContent
{
public NewsCoverTest()
private ShowMoreButton showMoreButton => overlay.ChildrenOfType<ShowMoreButton>().FirstOrDefault();
private void setUpNewsResponse(GetNewsResponse r, string testName = "Set up response")
=> AddStep(testName, () =>
{
Spacing = new osuTK.Vector2(0, 10);
var article = new NewsArticleCover.ArticleInfo
dummyAPI.HandleRequest = request =>
{
Author = "Ephemeral",
CoverUrl = "https://assets.ppy.sh/artists/58/header.jpg",
Time = new DateTime(2019, 12, 4),
Title = "New Featured Artist: Kurokotei"
};
if (!(request is GetNewsRequest getNewsRequest))
return false;
Children = new Drawable[]
{
new NewsArticleCover(article)
{
Height = 200
},
new NewsArticleCover(article)
{
Height = 120
},
new NewsArticleCover(article)
{
RelativeSizeAxes = Axes.None,
Size = new osuTK.Vector2(400, 200),
}
getNewsRequest.TriggerSuccess(r);
return true;
};
});
private static GetNewsResponse responseExample => new GetNewsResponse
{
NewsPosts = new[]
{
new APINewsPost
{
Title = "This post has an image which starts with \"/\" and has many authors!",
Preview = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
Author = "someone, someone1, someone2, someone3, someone4",
FirstImage = "/help/wiki/shared/news/banners/monthly-beatmapping-contest.png",
PublishedAt = DateTimeOffset.Now
},
new APINewsPost
{
Title = "This post has a full-url image! (HTML entity: &amp;)",
Preview = "boom (HTML entity: &amp;)",
Author = "user (HTML entity: &amp;)",
FirstImage = "https://assets.ppy.sh/artists/88/header.jpg",
PublishedAt = DateTimeOffset.Now
}
}
}
};
private static GetNewsResponse responseWithCursor => new GetNewsResponse
{
NewsPosts = new[]
{
new APINewsPost
{
Title = "This post has an image which starts with \"/\" and has many authors!",
Preview = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
Author = "someone, someone1, someone2, someone3, someone4",
FirstImage = "/help/wiki/shared/news/banners/monthly-beatmapping-contest.png",
PublishedAt = DateTimeOffset.Now
}
},
Cursor = new Cursor()
};
private static GetNewsResponse responseWithNoCursor => new GetNewsResponse
{
NewsPosts = new[]
{
new APINewsPost
{
Title = "This post has a full-url image! (HTML entity: &amp;)",
Preview = "boom (HTML entity: &amp;)",
Author = "user (HTML entity: &amp;)",
FirstImage = "https://assets.ppy.sh/artists/88/header.jpg",
PublishedAt = DateTimeOffset.Now
}
},
Cursor = null
};
}
}

View File

@ -0,0 +1,152 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Testing;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays;
using osu.Game.Overlays.News.Sidebar;
using static osu.Game.Overlays.News.Sidebar.YearsPanel;
namespace osu.Game.Tests.Visual.Online
{
public class TestSceneNewsSidebar : OsuTestScene
{
[Cached]
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
private TestNewsSidebar sidebar;
[SetUp]
public void SetUp() => Schedule(() => Child = sidebar = new TestNewsSidebar { YearChanged = onYearChanged });
[Test]
public void TestBasic()
{
AddStep("Add metadata", () => sidebar.Metadata.Value = getMetadata(2021));
AddUntilStep("Month sections exist", () => sidebar.ChildrenOfType<MonthSection>().Any());
}
[Test]
public void TestMetadataWithNoPosts()
{
AddStep("Add data with no posts", () => sidebar.Metadata.Value = metadata_with_no_posts);
AddUntilStep("No month sections were created", () => !sidebar.ChildrenOfType<MonthSection>().Any());
}
[Test]
public void TestYearsPanelVisibility()
{
AddUntilStep("Years panel is hidden", () => yearsPanel?.Alpha == 0);
AddStep("Add data", () => sidebar.Metadata.Value = getMetadata(2021));
AddUntilStep("Years panel is visible", () => yearsPanel?.Alpha == 1);
}
private void onYearChanged(int year) => sidebar.Metadata.Value = getMetadata(year);
private YearsPanel yearsPanel => sidebar.ChildrenOfType<YearsPanel>().FirstOrDefault();
private APINewsSidebar getMetadata(int year) => new APINewsSidebar
{
CurrentYear = year,
Years = new[]
{
2021,
2020,
2019,
2018,
2017,
2016,
2015,
2014,
2013
},
NewsPosts = new List<APINewsPost>
{
new APINewsPost
{
Title = "(Mar) Short title",
PublishedAt = new DateTime(year, 3, 1)
},
new APINewsPost
{
Title = "(Mar) Oh boy that's a long post title I wonder if it will break anything",
PublishedAt = new DateTime(year, 3, 1)
},
new APINewsPost
{
Title = "(Mar) Medium title, nothing to see here",
PublishedAt = new DateTime(year, 3, 1)
},
new APINewsPost
{
Title = "(Feb) Short title",
PublishedAt = new DateTime(year, 2, 1)
},
new APINewsPost
{
Title = "(Feb) Oh boy that's a long post title I wonder if it will break anything",
PublishedAt = new DateTime(year, 2, 1)
},
new APINewsPost
{
Title = "(Feb) Medium title, nothing to see here",
PublishedAt = new DateTime(year, 2, 1)
},
new APINewsPost
{
Title = "Short title",
PublishedAt = new DateTime(year, 1, 1)
},
new APINewsPost
{
Title = "Oh boy that's a long post title I wonder if it will break anything",
PublishedAt = new DateTime(year, 1, 1)
},
new APINewsPost
{
Title = "Medium title, nothing to see here",
PublishedAt = new DateTime(year, 1, 1)
}
}
};
private static readonly APINewsSidebar metadata_with_no_posts = new APINewsSidebar
{
CurrentYear = 2021,
Years = new[]
{
2021,
2020,
2019,
2018,
2017,
2016,
2015,
2014,
2013
},
NewsPosts = Array.Empty<APINewsPost>()
};
private class TestNewsSidebar : NewsSidebar
{
public Action<int> YearChanged;
protected override void LoadComplete()
{
base.LoadComplete();
Metadata.BindValueChanged(metadata =>
{
foreach (var b in this.ChildrenOfType<YearButton>())
b.Action = () => YearChanged?.Invoke(b.Year);
}, true);
}
}
}
}

View File

@ -6,6 +6,7 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Online.API;
using osu.Game.Online.Chat;
using osu.Game.Rulesets;
using osu.Game.Users;
@ -18,6 +19,8 @@ namespace osu.Game.Tests.Visual.Online
[Cached(typeof(IChannelPostTarget))]
private PostTarget postTarget { get; set; }
private DummyAPIAccess api => (DummyAPIAccess)API;
public TestSceneNowPlayingCommand()
{
Add(postTarget = new PostTarget());
@ -26,7 +29,7 @@ namespace osu.Game.Tests.Visual.Online
[Test]
public void TestGenericActivity()
{
AddStep("Set activity", () => API.Activity.Value = new UserActivity.InLobby());
AddStep("Set activity", () => api.Activity.Value = new UserActivity.InLobby(null));
AddStep("Run command", () => Add(new NowPlayingCommand()));
@ -36,7 +39,7 @@ namespace osu.Game.Tests.Visual.Online
[Test]
public void TestEditActivity()
{
AddStep("Set activity", () => API.Activity.Value = new UserActivity.Editing(new BeatmapInfo()));
AddStep("Set activity", () => api.Activity.Value = new UserActivity.Editing(new BeatmapInfo()));
AddStep("Run command", () => Add(new NowPlayingCommand()));
@ -46,7 +49,7 @@ namespace osu.Game.Tests.Visual.Online
[Test]
public void TestPlayActivity()
{
AddStep("Set activity", () => API.Activity.Value = new UserActivity.SoloGame(new BeatmapInfo(), new RulesetInfo()));
AddStep("Set activity", () => api.Activity.Value = new UserActivity.SoloGame(new BeatmapInfo(), new RulesetInfo()));
AddStep("Run command", () => Add(new NowPlayingCommand()));
@ -57,9 +60,9 @@ namespace osu.Game.Tests.Visual.Online
[TestCase(false)]
public void TestLinkPresence(bool hasOnlineId)
{
AddStep("Set activity", () => API.Activity.Value = new UserActivity.InLobby());
AddStep("Set activity", () => api.Activity.Value = new UserActivity.InLobby(null));
AddStep("Set beatmap", () => Beatmap.Value = new DummyWorkingBeatmap(null, null)
AddStep("Set beatmap", () => Beatmap.Value = new DummyWorkingBeatmap(Audio, null)
{
BeatmapInfo = { OnlineBeatmapID = hasOnlineId ? 1234 : (int?)null }
});
@ -67,7 +70,7 @@ namespace osu.Game.Tests.Visual.Online
AddStep("Run command", () => Add(new NowPlayingCommand()));
if (hasOnlineId)
AddAssert("Check link presence", () => postTarget.LastMessage.Contains("https://osu.ppy.sh/b/1234"));
AddAssert("Check link presence", () => postTarget.LastMessage.Contains("/b/1234"));
else
AddAssert("Check link not present", () => !postTarget.LastMessage.Contains("https://"));
}

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

@ -32,7 +32,7 @@ namespace osu.Game.Tests.Visual.Online
[Test]
public void TestOnlineStateVisibility()
{
AddStep("set status to online", () => ((DummyAPIAccess)API).State = APIState.Online);
AddStep("set status to online", () => ((DummyAPIAccess)API).SetState(APIState.Online));
AddUntilStep("children are visible", () => onlineView.ViewTarget.IsPresent);
AddUntilStep("loading animation is not visible", () => !onlineView.LoadingSpinner.IsPresent);
@ -41,7 +41,7 @@ namespace osu.Game.Tests.Visual.Online
[Test]
public void TestOfflineStateVisibility()
{
AddStep("set status to offline", () => ((DummyAPIAccess)API).State = APIState.Offline);
AddStep("set status to offline", () => ((DummyAPIAccess)API).SetState(APIState.Offline));
AddUntilStep("children are not visible", () => !onlineView.ViewTarget.IsPresent);
AddUntilStep("loading animation is not visible", () => !onlineView.LoadingSpinner.IsPresent);
@ -50,7 +50,7 @@ namespace osu.Game.Tests.Visual.Online
[Test]
public void TestConnectingStateVisibility()
{
AddStep("set status to connecting", () => ((DummyAPIAccess)API).State = APIState.Connecting);
AddStep("set status to connecting", () => ((DummyAPIAccess)API).SetState(APIState.Connecting));
AddUntilStep("children are not visible", () => !onlineView.ViewTarget.IsPresent);
AddUntilStep("loading animation is visible", () => onlineView.LoadingSpinner.IsPresent);
@ -59,7 +59,7 @@ namespace osu.Game.Tests.Visual.Online
[Test]
public void TestFailingStateVisibility()
{
AddStep("set status to failing", () => ((DummyAPIAccess)API).State = APIState.Failing);
AddStep("set status to failing", () => ((DummyAPIAccess)API).SetState(APIState.Failing));
AddUntilStep("children are not visible", () => !onlineView.ViewTarget.IsPresent);
AddUntilStep("loading animation is visible", () => onlineView.LoadingSpinner.IsPresent);

View File

@ -0,0 +1,181 @@
// 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.Profile.Sections.Historical;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Users;
using NUnit.Framework;
using osu.Game.Overlays;
using osu.Framework.Allocation;
using System;
using System.Linq;
using osu.Framework.Testing;
using osu.Framework.Graphics.Shapes;
using static osu.Game.Users.User;
namespace osu.Game.Tests.Visual.Online
{
public class TestScenePlayHistorySubsection : OsuTestScene
{
[Cached]
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Red);
private readonly Bindable<User> user = new Bindable<User>();
private readonly PlayHistorySubsection section;
public TestScenePlayHistorySubsection()
{
AddRange(new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = colourProvider.Background4
},
section = new PlayHistorySubsection(user)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre
}
});
}
[Test]
public void TestNullValues()
{
AddStep("Load user", () => user.Value = user_with_null_values);
AddAssert("Section is hidden", () => section.Alpha == 0);
}
[Test]
public void TestEmptyValues()
{
AddStep("Load user", () => user.Value = user_with_empty_values);
AddAssert("Section is hidden", () => section.Alpha == 0);
}
[Test]
public void TestOneValue()
{
AddStep("Load user", () => user.Value = user_with_one_value);
AddAssert("Section is hidden", () => section.Alpha == 0);
}
[Test]
public void TestTwoValues()
{
AddStep("Load user", () => user.Value = user_with_two_values);
AddAssert("Section is visible", () => section.Alpha == 1);
}
[Test]
public void TestConstantValues()
{
AddStep("Load user", () => user.Value = user_with_constant_values);
AddAssert("Section is visible", () => section.Alpha == 1);
}
[Test]
public void TestConstantZeroValues()
{
AddStep("Load user", () => user.Value = user_with_zero_values);
AddAssert("Section is visible", () => section.Alpha == 1);
}
[Test]
public void TestFilledValues()
{
AddStep("Load user", () => user.Value = user_with_filled_values);
AddAssert("Section is visible", () => section.Alpha == 1);
AddAssert("Array length is the same", () => user_with_filled_values.MonthlyPlaycounts.Length == getChartValuesLength());
}
[Test]
public void TestMissingValues()
{
AddStep("Load user", () => user.Value = user_with_missing_values);
AddAssert("Section is visible", () => section.Alpha == 1);
AddAssert("Array length is 7", () => getChartValuesLength() == 7);
}
private int getChartValuesLength() => this.ChildrenOfType<ProfileLineChart>().Single().Values.Length;
private static readonly User user_with_null_values = new User
{
Id = 1
};
private static readonly User user_with_empty_values = new User
{
Id = 2,
MonthlyPlaycounts = Array.Empty<UserHistoryCount>()
};
private static readonly User user_with_one_value = new User
{
Id = 3,
MonthlyPlaycounts = new[]
{
new UserHistoryCount { Date = new DateTime(2010, 5, 1), Count = 100 }
}
};
private static readonly User user_with_two_values = new User
{
Id = 4,
MonthlyPlaycounts = new[]
{
new UserHistoryCount { Date = new DateTime(2010, 5, 1), Count = 1 },
new UserHistoryCount { Date = new DateTime(2010, 6, 1), Count = 2 }
}
};
private static readonly User user_with_constant_values = new User
{
Id = 5,
MonthlyPlaycounts = new[]
{
new UserHistoryCount { Date = new DateTime(2010, 5, 1), Count = 5 },
new UserHistoryCount { Date = new DateTime(2010, 6, 1), Count = 5 },
new UserHistoryCount { Date = new DateTime(2010, 7, 1), Count = 5 }
}
};
private static readonly User user_with_zero_values = new User
{
Id = 6,
MonthlyPlaycounts = new[]
{
new UserHistoryCount { Date = new DateTime(2010, 5, 1), Count = 0 },
new UserHistoryCount { Date = new DateTime(2010, 6, 1), Count = 0 },
new UserHistoryCount { Date = new DateTime(2010, 7, 1), Count = 0 }
}
};
private static readonly User user_with_filled_values = new User
{
Id = 7,
MonthlyPlaycounts = new[]
{
new UserHistoryCount { Date = new DateTime(2010, 5, 1), Count = 1000 },
new UserHistoryCount { Date = new DateTime(2010, 6, 1), Count = 20 },
new UserHistoryCount { Date = new DateTime(2010, 7, 1), Count = 20000 },
new UserHistoryCount { Date = new DateTime(2010, 8, 1), Count = 30 },
new UserHistoryCount { Date = new DateTime(2010, 9, 1), Count = 50 },
new UserHistoryCount { Date = new DateTime(2010, 10, 1), Count = 2000 },
new UserHistoryCount { Date = new DateTime(2010, 11, 1), Count = 2100 }
}
};
private static readonly User user_with_missing_values = new User
{
Id = 8,
MonthlyPlaycounts = new[]
{
new UserHistoryCount { Date = new DateTime(2020, 1, 1), Count = 100 },
new UserHistoryCount { Date = new DateTime(2020, 7, 1), Count = 200 }
}
};
}
}

View File

@ -1,40 +0,0 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Overlays;
using osu.Game.Overlays.Profile.Sections;
namespace osu.Game.Tests.Visual.Online
{
public class TestSceneProfileCounterPill : OsuTestScene
{
[Cached]
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Red);
private readonly CounterPill pill;
private readonly BindableInt value = new BindableInt();
public TestSceneProfileCounterPill()
{
Child = pill = new CounterPill
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Current = { BindTarget = value }
};
}
[Test]
public void TestVisibility()
{
AddStep("Set value to 0", () => value.Value = 0);
AddAssert("Check hidden", () => !pill.IsPresent);
AddStep("Set value to 10", () => value.Value = 10);
AddAssert("Check visible", () => pill.IsPresent);
}
}
}

View File

@ -70,7 +70,7 @@ namespace osu.Game.Tests.Visual.Online
{
graph.Statistics.Value = new UserStatistics
{
Ranks = new UserStatistics.UserRanks { Global = 123456 },
GlobalRank = 123456,
PP = 12345,
};
});
@ -79,7 +79,7 @@ namespace osu.Game.Tests.Visual.Online
{
graph.Statistics.Value = new UserStatistics
{
Ranks = new UserStatistics.UserRanks { Global = 89000 },
GlobalRank = 89000,
PP = 12345,
RankHistory = new User.RankHistoryData
{
@ -92,7 +92,7 @@ namespace osu.Game.Tests.Visual.Online
{
graph.Statistics.Value = new UserStatistics
{
Ranks = new UserStatistics.UserRanks { Global = 89000 },
GlobalRank = 89000,
PP = 12345,
RankHistory = new User.RankHistoryData
{
@ -105,7 +105,7 @@ namespace osu.Game.Tests.Visual.Online
{
graph.Statistics.Value = new UserStatistics
{
Ranks = new UserStatistics.UserRanks { Global = 12000 },
GlobalRank = 12000,
PP = 12345,
RankHistory = new User.RankHistoryData
{
@ -118,7 +118,7 @@ namespace osu.Game.Tests.Visual.Online
{
graph.Statistics.Value = new UserStatistics
{
Ranks = new UserStatistics.UserRanks { Global = 12000 },
GlobalRank = 12000,
PP = 12345,
RankHistory = new User.RankHistoryData
{

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

@ -4,19 +4,22 @@
using osu.Framework.Graphics;
using osu.Game.Graphics.UserInterface;
using osu.Framework.Allocation;
using osu.Game.Graphics;
using osu.Game.Overlays;
namespace osu.Game.Tests.Visual.Online
{
public class TestSceneShowMoreButton : OsuTestScene
{
[Cached]
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green);
public TestSceneShowMoreButton()
{
TestButton button = null;
ShowMoreButton button = null;
int fireCount = 0;
Add(button = new TestButton
Add(button = new ShowMoreButton
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@ -29,33 +32,22 @@ namespace osu.Game.Tests.Visual.Online
}
});
AddStep("click button", () => button.Click());
AddStep("click button", () => button.TriggerClick());
AddAssert("action fired once", () => fireCount == 1);
AddAssert("is in loading state", () => button.IsLoading);
AddStep("click button", () => button.Click());
AddStep("click button", () => button.TriggerClick());
AddAssert("action not fired", () => fireCount == 1);
AddAssert("is in loading state", () => button.IsLoading);
AddUntilStep("wait for loaded", () => !button.IsLoading);
AddStep("click button", () => button.Click());
AddStep("click button", () => button.TriggerClick());
AddAssert("action fired twice", () => fireCount == 2);
AddAssert("is in loading state", () => button.IsLoading);
}
private class TestButton : ShowMoreButton
{
[BackgroundDependencyLoader]
private void load(OsuColour colors)
{
IdleColour = colors.YellowDark;
HoverColour = colors.Yellow;
ChevronIconColour = colors.Red;
}
}
}
}

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,119 @@ 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;
[Test]
public void TestSystemMessageOrdering()
{
var standardMessage = new Message(messageIdSequence++)
{
Sender = admin,
Content = "I am a wang!"
};
int sequence = 0;
var infoMessage1 = new InfoMessage($"the system is calling {messageIdSequence++}");
var infoMessage2 = new InfoMessage($"the system is calling {messageIdSequence++}");
AddStep("message from admin", () => testChannel.AddNewMessages(new Message(sequence++)
AddStep("message from admin", () => testChannel.AddNewMessages(standardMessage));
AddStep("message from system", () => testChannel.AddNewMessages(infoMessage1));
AddStep("message from system", () => testChannel.AddNewMessages(infoMessage2));
AddAssert("message order is correct", () => testChannel.Messages.Count == 3
&& testChannel.Messages[0] == standardMessage
&& testChannel.Messages[1] == infoMessage1
&& testChannel.Messages[2] == infoMessage2);
}
[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 +180,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 +316,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

@ -0,0 +1,56 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using osu.Game.Overlays.Profile.Sections.Historical;
using osu.Framework.Graphics;
using osu.Game.Overlays;
using osu.Framework.Allocation;
using static osu.Game.Users.User;
namespace osu.Game.Tests.Visual.Online
{
public class TestSceneUserHistoryGraph : OsuTestScene
{
[Cached]
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Pink);
public TestSceneUserHistoryGraph()
{
UserHistoryGraph graph;
Add(graph = new UserHistoryGraph("Test")
{
RelativeSizeAxes = Axes.X,
Height = 200,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
});
var values = new[]
{
new UserHistoryCount { Date = new DateTime(2000, 1, 1), Count = 10 },
new UserHistoryCount { Date = new DateTime(2000, 2, 1), Count = 20 },
new UserHistoryCount { Date = new DateTime(2000, 3, 1), Count = 100 },
new UserHistoryCount { Date = new DateTime(2000, 4, 1), Count = 15 },
new UserHistoryCount { Date = new DateTime(2000, 5, 1), Count = 30 }
};
var moreValues = new[]
{
new UserHistoryCount { Date = new DateTime(2010, 5, 1), Count = 1000 },
new UserHistoryCount { Date = new DateTime(2010, 6, 1), Count = 20 },
new UserHistoryCount { Date = new DateTime(2010, 7, 1), Count = 20000 },
new UserHistoryCount { Date = new DateTime(2010, 8, 1), Count = 30 },
new UserHistoryCount { Date = new DateTime(2010, 9, 1), Count = 50 },
new UserHistoryCount { Date = new DateTime(2010, 10, 1), Count = 2000 },
new UserHistoryCount { Date = new DateTime(2010, 11, 1), Count = 2100 }
};
AddStep("Set fake values", () => graph.Values = values);
AddStep("Set more values", () => graph.Values = moreValues);
AddStep("Set null values", () => graph.Values = null);
AddStep("Set empty values", () => graph.Values = Array.Empty<UserHistoryCount>());
}
}
}

View File

@ -33,7 +33,8 @@ namespace osu.Game.Tests.Visual.Online
ProfileOrder = new[] { "me" },
Statistics = new UserStatistics
{
Ranks = new UserStatistics.UserRanks { Global = 2148, Country = 1 },
GlobalRank = 2148,
CountryRank = 1,
PP = 4567.89m,
Level = new UserStatistics.LevelInfo
{

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,33 +18,56 @@ 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()));
AddStep("Click", () => votePill.Click());
AddAssert("Background is transparent", () => votePill.Background.Alpha == 0);
AddStep("Click", () => votePill.TriggerClick());
AddAssert("Not loading", () => !votePill.IsLoading);
}
[Test]
public void TestRandomCommentPill()
{
AddStep("Hide login overlay", () => login.Hide());
AddStep("Log in", logIn);
AddStep("Random comment", () => addVotePill(getRandomComment()));
AddStep("Click", () => votePill.Click());
AddAssert("Background is visible", () => votePill.Background.Alpha == 1);
AddStep("Click", () => votePill.TriggerClick());
AddAssert("Loading", () => votePill.IsLoading);
}
[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);
AddStep("Click", () => votePill.TriggerClick());
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

@ -0,0 +1,103 @@
// 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.Localisation;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays;
using osu.Game.Overlays.Wiki;
namespace osu.Game.Tests.Visual.Online
{
public class TestSceneWikiHeader : OsuTestScene
{
[Cached]
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Orange);
[Cached]
private readonly Bindable<APIWikiPage> wikiPageData = new Bindable<APIWikiPage>(new APIWikiPage
{
Title = "Main Page",
Path = "Main_Page",
});
private TestHeader header;
[SetUp]
public void SetUp() => Schedule(() =>
{
Child = header = new TestHeader
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
ShowIndexPage = dummyShowIndexPage,
ShowParentPage = dummyShowParentPage,
};
wikiPageData.BindTo(header.WikiPageData);
});
[Test]
public void TestWikiHeader()
{
AddAssert("Current is index", () => checkCurrent(WikiHeader.IndexPageString));
AddStep("Change wiki page data", () => wikiPageData.Value = new APIWikiPage
{
Title = "Welcome",
Path = "Welcome"
});
AddAssert("Current is welcome", () => checkCurrent("Welcome"));
AddAssert("Check breadcrumb", checkBreadcrumb);
AddStep("Change current to index", () => header.Current.Value = WikiHeader.IndexPageString);
AddAssert("Current is index", () => checkCurrent(WikiHeader.IndexPageString));
AddStep("Change wiki page data", () => wikiPageData.Value = new APIWikiPage
{
Title = "Developers",
Path = "People/The_Team/Developers",
Subtitle = "The Team",
});
AddAssert("Current is 'Developers'", () => checkCurrent("Developers"));
AddAssert("Check breadcrumb", checkBreadcrumb);
AddStep("Change current to 'The Team'", () => header.Current.Value = "The Team");
AddAssert("Current is 'The Team'", () => checkCurrent("The Team"));
AddAssert("Check breadcrumb", checkBreadcrumb);
}
private bool checkCurrent(LocalisableString expectedCurrent) => header.Current.Value == expectedCurrent;
private bool checkBreadcrumb()
{
var result = header.TabControlItems.Contains(wikiPageData.Value.Title);
if (wikiPageData.Value.Subtitle != null)
result = header.TabControlItems.Contains(wikiPageData.Value.Subtitle) && result;
return result;
}
private void dummyShowIndexPage() => wikiPageData.SetDefault();
private void dummyShowParentPage()
{
wikiPageData.Value = new APIWikiPage
{
Path = "People/The_Team",
Title = "The Team",
Subtitle = "People"
};
}
private class TestHeader : WikiHeader
{
public IReadOnlyList<LocalisableString?> TabControlItems => TabControl.Items;
}
}
}

View File

@ -0,0 +1,43 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Overlays;
using osu.Game.Overlays.Wiki;
namespace osu.Game.Tests.Visual.Online
{
public class TestSceneWikiMainPage : OsuTestScene
{
[Cached]
private readonly OverlayColourProvider overlayColour = new OverlayColourProvider(OverlayColourScheme.Orange);
public TestSceneWikiMainPage()
{
Children = new Drawable[]
{
new Box
{
Colour = overlayColour.Background5,
RelativeSizeAxes = Axes.Both,
},
new BasicScrollContainer
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding(20),
Child = new WikiMainPage
{
Markdown = main_page_markdown
}
}
};
}
// From https://osu.ppy.sh/api/v2/wiki/en/Main_Page
private const string main_page_markdown =
"---\nlayout: main_page\n---\n\n<!-- Do not add any empty lines inside this div. -->\n\n<div class=\"wiki-main-page__blurb\">\nWelcome to the osu! wiki, a project containing a wide range of osu! related information.\n</div>\n\n<div class=\"wiki-main-page__panels\">\n<div class=\"wiki-main-page-panel wiki-main-page-panel--full\">\n\n# Getting started\n\n[Welcome](/wiki/Welcome) • [Installation](/wiki/Installation) • [Registration](/wiki/Registration) • [Help Centre](/wiki/Help_Centre) • [FAQ](/wiki/FAQ)\n\n</div>\n<div class=\"wiki-main-page-panel\">\n\n# Game client\n\n[Interface](/wiki/Interface) • [Options](/wiki/Options) • [Visual settings](/wiki/Visual_Settings) • [Shortcut key reference](/wiki/Shortcut_key_reference) • [Configuration file](/wiki/osu!_Program_Files/User_Configuration_File) • [Program files](/wiki/osu!_Program_Files)\n\n[File formats](/wiki/osu!_File_Formats): [.osz](/wiki/osu!_File_Formats/Osz_(file_format)) • [.osk](/wiki/osu!_File_Formats/Osk_(file_format)) • [.osr](/wiki/osu!_File_Formats/Osr_(file_format)) • [.osu](/wiki/osu!_File_Formats/Osu_(file_format)) • [.osb](/wiki/osu!_File_Formats/Osb_(file_format)) • [.db](/wiki/osu!_File_Formats/Db_(file_format))\n\n</div>\n<div class=\"wiki-main-page-panel\">\n\n# Gameplay\n\n[Game modes](/wiki/Game_mode): [osu!](/wiki/Game_mode/osu!) • [osu!taiko](/wiki/Game_mode/osu!taiko) • [osu!catch](/wiki/Game_mode/osu!catch) • [osu!mania](/wiki/Game_mode/osu!mania)\n\n[Beatmap](/wiki/Beatmap) • [Hit object](/wiki/Hit_object) • [Mods](/wiki/Game_modifier) • [Score](/wiki/Score) • [Replay](/wiki/Replay) • [Multi](/wiki/Multi)\n\n</div>\n<div class=\"wiki-main-page-panel\">\n\n# [Beatmap editor](/wiki/Beatmap_Editor)\n\nSections: [Compose](/wiki/Beatmap_Editor/Compose) • [Design](/wiki/Beatmap_Editor/Design) • [Timing](/wiki/Beatmap_Editor/Timing) • [Song setup](/wiki/Beatmap_Editor/Song_Setup)\n\nComponents: [AiMod](/wiki/Beatmap_Editor/AiMod) • [Beat snap divisor](/wiki/Beatmap_Editor/Beat_Snap_Divisor) • [Distance snap](/wiki/Beatmap_Editor/Distance_Snap) • [Menu](/wiki/Beatmap_Editor/Menu) • [SB load](/wiki/Beatmap_Editor/SB_Load) • [Timelines](/wiki/Beatmap_Editor/Timelines)\n\n[Beatmapping](/wiki/Beatmapping) • [Difficulty](/wiki/Beatmap/Difficulty) • [Mapping techniques](/wiki/Mapping_Techniques) • [Storyboarding](/wiki/Storyboarding)\n\n</div>\n<div class=\"wiki-main-page-panel\">\n\n# Beatmap submission and ranking\n\n[Submission](/wiki/Submission) • [Modding](/wiki/Modding) • [Ranking procedure](/wiki/Beatmap_ranking_procedure) • [Mappers' Guild](/wiki/Mappers_Guild) • [Project Loved](/wiki/Project_Loved)\n\n[Ranking criteria](/wiki/Ranking_Criteria): [osu!](/wiki/Ranking_Criteria/osu!) • [osu!taiko](/wiki/Ranking_Criteria/osu!taiko) • [osu!catch](/wiki/Ranking_Criteria/osu!catch) • [osu!mania](/wiki/Ranking_Criteria/osu!mania)\n\n</div>\n<div class=\"wiki-main-page-panel\">\n\n# Community\n\n[Tournaments](/wiki/Tournaments) • [Skinning](/wiki/Skinning) • [Projects](/wiki/Projects) • [Guides](/wiki/Guides) • [osu!dev Discord server](/wiki/osu!dev_Discord_server) • [How you can help](/wiki/How_You_Can_Help!) • [Glossary](/wiki/Glossary)\n\n</div>\n<div class=\"wiki-main-page-panel\">\n\n# People\n\n[The Team](/wiki/People/The_Team): [Developers](/wiki/People/The_Team/Developers) • [Global Moderation Team](/wiki/People/The_Team/Global_Moderation_Team) • [Support Team](/wiki/People/The_Team/Support_Team) • [Nomination Assessment Team](/wiki/People/The_Team/Nomination_Assessment_Team) • [Beatmap Nominators](/wiki/People/The_Team/Beatmap_Nominators) • [osu! Alumni](/wiki/People/The_Team/osu!_Alumni) • [Project Loved Team](/wiki/People/The_Team/Project_Loved_Team)\n\nOrganisations: [osu! UCI](/wiki/Organisations/osu!_UCI)\n\n[Community Contributors](/wiki/People/Community_Contributors) • [Users with unique titles](/wiki/People/Users_with_unique_titles)\n\n</div>\n<div class=\"wiki-main-page-panel\">\n\n# For developers\n\n[API](/wiki/osu!api) • [Bot account](/wiki/Bot_account) • [Brand identity guidelines](/wiki/Brand_identity_guidelines)\n\n</div>\n<div class=\"wiki-main-page-panel\">\n\n# About the wiki\n\n[Sitemap](/wiki/Sitemap) • [Contribution guide](/wiki/osu!_wiki_Contribution_Guide) • [Article styling criteria](/wiki/Article_Styling_Criteria) • [News styling criteria](/wiki/News_Styling_Criteria)\n\n</div>\n</div>\n";
}
}

View File

@ -0,0 +1,214 @@
// 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 Markdig.Syntax.Inlines;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Containers.Markdown;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Graphics.Containers.Markdown;
using osu.Game.Overlays;
using osu.Game.Overlays.Wiki.Markdown;
namespace osu.Game.Tests.Visual.Online
{
public class TestSceneWikiMarkdownContainer : OsuTestScene
{
private TestMarkdownContainer markdownContainer;
[Cached]
private readonly OverlayColourProvider overlayColour = new OverlayColourProvider(OverlayColourScheme.Orange);
[SetUp]
public void Setup() => Schedule(() =>
{
Children = new Drawable[]
{
new Box
{
Colour = overlayColour.Background5,
RelativeSizeAxes = Axes.Both,
},
new BasicScrollContainer
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding(20),
Child = markdownContainer = new TestMarkdownContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
}
}
};
});
[Test]
public void TestLink()
{
AddStep("set current path", () => markdownContainer.CurrentPath = $"{API.WebsiteRootUrl}/wiki/Article_styling_criteria/");
AddStep("set '/wiki/Main_Page''", () => markdownContainer.Text = "[wiki main page](/wiki/Main_Page)");
AddAssert("check url", () => markdownContainer.Link.Url == $"{API.WebsiteRootUrl}/wiki/Main_Page");
AddStep("set '../FAQ''", () => markdownContainer.Text = "[FAQ](../FAQ)");
AddAssert("check url", () => markdownContainer.Link.Url == $"{API.WebsiteRootUrl}/wiki/FAQ");
AddStep("set './Writing''", () => markdownContainer.Text = "[wiki writing guidline](./Writing)");
AddAssert("check url", () => markdownContainer.Link.Url == $"{API.WebsiteRootUrl}/wiki/Article_styling_criteria/Writing");
AddStep("set 'Formatting''", () => markdownContainer.Text = "[wiki formatting guidline](Formatting)");
AddAssert("check url", () => markdownContainer.Link.Url == $"{API.WebsiteRootUrl}/wiki/Article_styling_criteria/Formatting");
}
[Test]
public void TestOutdatedNoticeBox()
{
AddStep("Add outdated yaml header", () =>
{
markdownContainer.Text = @"---
outdated: true
---";
});
}
[Test]
public void TestNeedsCleanupNoticeBox()
{
AddStep("Add needs cleanup yaml header", () =>
{
markdownContainer.Text = @"---
needs_cleanup: true
---";
});
}
[Test]
public void TestOnlyShowOutdatedNoticeBox()
{
AddStep("Add outdated and needs cleanup yaml", () =>
{
markdownContainer.Text = @"---
outdated: true
needs_cleanup: true
---";
});
}
[Test]
public void TestAbsoluteImage()
{
AddStep("Add absolute image", () =>
{
markdownContainer.CurrentPath = "https://dev.ppy.sh";
markdownContainer.Text = "![intro](/wiki/Interface/img/intro-screen.jpg)";
});
}
[Test]
public void TestRelativeImage()
{
AddStep("Add relative image", () =>
{
markdownContainer.CurrentPath = "https://dev.ppy.sh/wiki/Interface/";
markdownContainer.Text = "![intro](img/intro-screen.jpg)";
});
}
[Test]
public void TestBlockImage()
{
AddStep("Add paragraph with block image", () =>
{
markdownContainer.CurrentPath = "https://dev.ppy.sh/wiki/Interface/";
markdownContainer.Text = @"Line before image
![play menu](img/play-menu.jpg ""Main Menu in osu!"")
Line after image";
});
}
[Test]
public void TestInlineImage()
{
AddStep("Add inline image", () =>
{
markdownContainer.CurrentPath = "https://dev.ppy.sh";
markdownContainer.Text = "![osu! mode icon](/wiki/shared/mode/osu.png) osu!";
});
}
[Test]
public void TestTableWithImageContent()
{
AddStep("Add Table", () =>
{
markdownContainer.CurrentPath = "https://dev.ppy.sh";
markdownContainer.Text = @"
| Image | Name | Effect |
| :-: | :-: | :-- |
| ![](/wiki/Skinning/Interface/img/hit300.png ""300"") | 300 | A possible score when tapping a hit circle precisely on time, completing a Slider and keeping the cursor over every tick, or completing a Spinner with the Spinner Metre full. A score of 300 appears in an blue score by default. Scoring nothing except 300s in a beatmap will award the player with the SS or SSH grade. |
| ![](/wiki/Skinning/Interface/img/hit300g.png ""Geki"") | (激) Geki | A term from Ouendan, called Elite Beat! in EBA. Appears when playing the last element in a combo in which the player has scored only 300s. Getting a Geki will give a sizable boost to the Life Bar. By default, it is blue. |
| ![](/wiki/Skinning/Interface/img/hit100.png ""100"") | 100 | A possible score one can get when tapping a Hit Object slightly late or early, completing a Slider and missing a number of ticks, or completing a Spinner with the Spinner Meter almost full. A score of 100 appears in a green score by default. When very skilled players test a beatmap and they get a lot of 100s, this may mean that the beatmap does not have correct timing. |
| ![](/wiki/Skinning/Interface/img/hit300k.png ""300 Katu"") ![](/wiki/Skinning/Interface/img/hit100k.png ""100 Katu"") | (喝) Katu or Katsu | A term from Ouendan, called Beat! in EBA. Appears when playing the last element in a combo in which the player has scored at least one 100, but no 50s or misses. Getting a Katu will give a small boost to the Life Bar. By default, it is coloured green or blue depending on whether the Katu itself is a 100 or a 300. |
| ![](/wiki/Skinning/Interface/img/hit50.png ""50"") | 50 | A possible score one can get when tapping a hit circle rather early or late but not early or late enough to cause a miss, completing a Slider and missing a lot of ticks, or completing a Spinner with the Spinner Metre close to full. A score of 50 appears in a orange score by default. Scoring a 50 in a combo will prevent the appearance of a Katu or a Geki at the combo's end. |
| ![](/wiki/Skinning/Interface/img/hit0.png ""Miss"") | Miss | A possible score one can get when not tapping a hit circle or too early (based on OD and AR, it may *shake* instead), not tapping or holding the Slider at least once, or completing a Spinner with low Spinner Metre fill. Scoring a Miss will reset the current combo to 0 and will prevent the appearance of a Katu or a Geki at the combo's end. |
";
});
}
[Test]
public void TestWideImageNotExceedContainer()
{
AddStep("Add image", () =>
{
markdownContainer.CurrentPath = "https://dev.ppy.sh/wiki/osu!_Program_Files/";
markdownContainer.Text = "![](img/file_structure.jpg \"The file structure of osu!'s installation folder, on Windows and macOS\")";
});
AddUntilStep("Wait image to load", () => markdownContainer.ChildrenOfType<DelayedLoadWrapper>().First().DelayedLoadCompleted);
AddStep("Change container width", () =>
{
markdownContainer.Width = 0.5f;
});
AddAssert("Image not exceed container width", () =>
{
var spriteImage = markdownContainer.ChildrenOfType<Sprite>().First();
return Precision.DefinitelyBigger(markdownContainer.DrawWidth, spriteImage.DrawWidth);
});
}
private class TestMarkdownContainer : WikiMarkdownContainer
{
public LinkInline Link;
public override MarkdownTextFlowContainer CreateTextFlow() => new TestMarkdownTextFlowContainer
{
UrlAdded = link => Link = link,
};
private class TestMarkdownTextFlowContainer : OsuMarkdownTextFlowContainer
{
public Action<LinkInline> UrlAdded;
protected override void AddLinkText(string text, LinkInline linkInline)
{
base.AddLinkText(text, linkInline);
UrlAdded?.Invoke(linkInline);
}
protected override void AddImage(LinkInline linkInline) => AddDrawable(new WikiMarkdownImage(linkInline));
}
}
}
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,65 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using Markdig.Parsers;
using Markdig.Syntax;
using Markdig.Syntax.Inlines;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Game.Graphics.Containers.Markdown;
using osu.Game.Overlays;
using osu.Game.Overlays.Wiki;
namespace osu.Game.Tests.Visual.Online
{
public class TestSceneWikiSidebar : OsuTestScene
{
[Cached]
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Orange);
[Cached]
private readonly OverlayScrollContainer scrollContainer = new OverlayScrollContainer();
private WikiSidebar sidebar;
[SetUp]
public void SetUp() => Schedule(() => Child = sidebar = new WikiSidebar());
[Test]
public void TestNoContent()
{
AddStep("No Content", () => { });
}
[Test]
public void TestOnlyMainTitle()
{
AddStep("Add TOC", () =>
{
for (var i = 0; i < 10; i++)
addTitle($"This is a very long title {i + 1}");
});
}
[Test]
public void TestWithSubtitle()
{
AddStep("Add TOC", () =>
{
for (var i = 0; i < 10; i++)
addTitle($"This is a very long title {i + 1}", i % 4 != 0);
});
}
private void addTitle(string text, bool subtitle = false)
{
var headingBlock = new HeadingBlock(new HeadingBlockParser())
{
Inline = new ContainerInline().AppendChild(new LiteralInline(text)),
Level = subtitle ? 3 : 2,
};
var heading = new OsuMarkdownHeading(headingBlock);
sidebar.AddEntry(headingBlock, heading);
}
}
}