diff --git a/osu.Android.props b/osu.Android.props
index d7f29beeb3..f61ff79b9f 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -10,7 +10,7 @@
true
-
+
+
+
+
+ XamarinJetbrainsAnnotations
+
+
+
diff --git a/osu.Game.Tests/Beatmaps/BeatmapUpdaterMetadataLookupTest.cs b/osu.Game.Tests/Beatmaps/BeatmapUpdaterMetadataLookupTest.cs
index 84195f1e7c..11c4c54ea6 100644
--- a/osu.Game.Tests/Beatmaps/BeatmapUpdaterMetadataLookupTest.cs
+++ b/osu.Game.Tests/Beatmaps/BeatmapUpdaterMetadataLookupTest.cs
@@ -28,7 +28,12 @@ namespace osu.Game.Tests.Beatmaps
[Test]
public void TestLocalCacheQueriedFirst()
{
- var localLookupResult = new OnlineBeatmapMetadata { BeatmapID = 123456, BeatmapStatus = BeatmapOnlineStatus.Ranked };
+ var localLookupResult = new OnlineBeatmapMetadata
+ {
+ BeatmapID = 123456,
+ BeatmapStatus = BeatmapOnlineStatus.Ranked,
+ BeatmapSetStatus = BeatmapOnlineStatus.Ranked,
+ };
localCachedMetadataSourceMock.Setup(src => src.Available).Returns(true);
localCachedMetadataSourceMock.Setup(src => src.TryLookup(It.IsAny(), out localLookupResult))
.Returns(true);
@@ -42,6 +47,7 @@ namespace osu.Game.Tests.Beatmaps
metadataLookup.Update(beatmapSet, preferOnlineFetch: false);
Assert.That(beatmap.Status, Is.EqualTo(BeatmapOnlineStatus.Ranked));
+ Assert.That(beatmapSet.Status, Is.EqualTo(BeatmapOnlineStatus.Ranked));
localCachedMetadataSourceMock.Verify(src => src.TryLookup(beatmap, out It.Ref.IsAny!), Times.Once);
apiMetadataSourceMock.Verify(src => src.TryLookup(It.IsAny(), out It.Ref.IsAny!), Times.Never);
}
@@ -54,7 +60,12 @@ namespace osu.Game.Tests.Beatmaps
localCachedMetadataSourceMock.Setup(src => src.TryLookup(It.IsAny(), out localLookupResult))
.Returns(false);
- var onlineLookupResult = new OnlineBeatmapMetadata { BeatmapID = 123456, BeatmapStatus = BeatmapOnlineStatus.Ranked };
+ var onlineLookupResult = new OnlineBeatmapMetadata
+ {
+ BeatmapID = 123456,
+ BeatmapStatus = BeatmapOnlineStatus.Ranked,
+ BeatmapSetStatus = BeatmapOnlineStatus.Ranked,
+ };
apiMetadataSourceMock.Setup(src => src.Available).Returns(true);
apiMetadataSourceMock.Setup(src => src.TryLookup(It.IsAny(), out onlineLookupResult))
.Returns(true);
@@ -66,6 +77,7 @@ namespace osu.Game.Tests.Beatmaps
metadataLookup.Update(beatmapSet, preferOnlineFetch: false);
Assert.That(beatmap.Status, Is.EqualTo(BeatmapOnlineStatus.Ranked));
+ Assert.That(beatmapSet.Status, Is.EqualTo(BeatmapOnlineStatus.Ranked));
localCachedMetadataSourceMock.Verify(src => src.TryLookup(beatmap, out It.Ref.IsAny!), Times.Once);
apiMetadataSourceMock.Verify(src => src.TryLookup(beatmap, out It.Ref.IsAny!), Times.Once);
}
@@ -73,12 +85,22 @@ namespace osu.Game.Tests.Beatmaps
[Test]
public void TestPreferOnlineFetch()
{
- var localLookupResult = new OnlineBeatmapMetadata { BeatmapID = 123456, BeatmapStatus = BeatmapOnlineStatus.Ranked };
+ var localLookupResult = new OnlineBeatmapMetadata
+ {
+ BeatmapID = 123456,
+ BeatmapStatus = BeatmapOnlineStatus.Ranked,
+ BeatmapSetStatus = BeatmapOnlineStatus.Ranked,
+ };
localCachedMetadataSourceMock.Setup(src => src.Available).Returns(true);
localCachedMetadataSourceMock.Setup(src => src.TryLookup(It.IsAny(), out localLookupResult))
.Returns(true);
- var onlineLookupResult = new OnlineBeatmapMetadata { BeatmapID = 123456, BeatmapStatus = BeatmapOnlineStatus.Graveyard };
+ var onlineLookupResult = new OnlineBeatmapMetadata
+ {
+ BeatmapID = 123456,
+ BeatmapStatus = BeatmapOnlineStatus.Graveyard,
+ BeatmapSetStatus = BeatmapOnlineStatus.Graveyard,
+ };
apiMetadataSourceMock.Setup(src => src.Available).Returns(true);
apiMetadataSourceMock.Setup(src => src.TryLookup(It.IsAny(), out onlineLookupResult))
.Returns(true);
@@ -90,6 +112,7 @@ namespace osu.Game.Tests.Beatmaps
metadataLookup.Update(beatmapSet, preferOnlineFetch: true);
Assert.That(beatmap.Status, Is.EqualTo(BeatmapOnlineStatus.Graveyard));
+ Assert.That(beatmapSet.Status, Is.EqualTo(BeatmapOnlineStatus.Graveyard));
localCachedMetadataSourceMock.Verify(src => src.TryLookup(beatmap, out It.Ref.IsAny!), Times.Never);
apiMetadataSourceMock.Verify(src => src.TryLookup(beatmap, out It.Ref.IsAny!), Times.Once);
}
@@ -97,7 +120,12 @@ namespace osu.Game.Tests.Beatmaps
[Test]
public void TestPreferOnlineFetchFallsBackToLocalCacheIfOnlineSourceUnavailable()
{
- var localLookupResult = new OnlineBeatmapMetadata { BeatmapID = 123456, BeatmapStatus = BeatmapOnlineStatus.Ranked };
+ var localLookupResult = new OnlineBeatmapMetadata
+ {
+ BeatmapID = 123456,
+ BeatmapStatus = BeatmapOnlineStatus.Ranked,
+ BeatmapSetStatus = BeatmapOnlineStatus.Ranked,
+ };
localCachedMetadataSourceMock.Setup(src => src.Available).Returns(true);
localCachedMetadataSourceMock.Setup(src => src.TryLookup(It.IsAny(), out localLookupResult))
.Returns(true);
@@ -111,6 +139,7 @@ namespace osu.Game.Tests.Beatmaps
metadataLookup.Update(beatmapSet, preferOnlineFetch: true);
Assert.That(beatmap.Status, Is.EqualTo(BeatmapOnlineStatus.Ranked));
+ Assert.That(beatmapSet.Status, Is.EqualTo(BeatmapOnlineStatus.Ranked));
localCachedMetadataSourceMock.Verify(src => src.TryLookup(beatmap, out It.Ref.IsAny!), Times.Once);
apiMetadataSourceMock.Verify(src => src.TryLookup(beatmap, out It.Ref.IsAny!), Times.Never);
}
@@ -135,6 +164,7 @@ namespace osu.Game.Tests.Beatmaps
metadataLookup.Update(beatmapSet, preferOnlineFetch: false);
Assert.That(beatmap.Status, Is.EqualTo(BeatmapOnlineStatus.None));
+ Assert.That(beatmapSet.Status, Is.EqualTo(BeatmapOnlineStatus.None));
Assert.That(beatmap.OnlineID, Is.EqualTo(-1));
localCachedMetadataSourceMock.Verify(src => src.TryLookup(beatmap, out It.Ref.IsAny!), Times.Once);
apiMetadataSourceMock.Verify(src => src.TryLookup(beatmap, out It.Ref.IsAny!), Times.Once);
@@ -163,6 +193,7 @@ namespace osu.Game.Tests.Beatmaps
metadataLookup.Update(beatmapSet, preferOnlineFetch);
Assert.That(beatmap.Status, Is.EqualTo(BeatmapOnlineStatus.None));
+ Assert.That(beatmapSet.Status, Is.EqualTo(BeatmapOnlineStatus.None));
Assert.That(beatmap.OnlineID, Is.EqualTo(123456));
}
@@ -193,5 +224,217 @@ namespace osu.Game.Tests.Beatmaps
Assert.That(beatmap.OnlineID, Is.EqualTo(123456));
}
+
+ [Test]
+ public void TestReturnedMetadataHasDifferentOnlineID([Values] bool preferOnlineFetch)
+ {
+ var lookupResult = new OnlineBeatmapMetadata { BeatmapID = 654321, BeatmapStatus = BeatmapOnlineStatus.Ranked };
+
+ var targetMock = preferOnlineFetch ? apiMetadataSourceMock : localCachedMetadataSourceMock;
+ targetMock.Setup(src => src.Available).Returns(true);
+ targetMock.Setup(src => src.TryLookup(It.IsAny(), out lookupResult))
+ .Returns(true);
+
+ var beatmap = new BeatmapInfo { OnlineID = 123456 };
+ var beatmapSet = new BeatmapSetInfo(beatmap.Yield());
+ beatmap.BeatmapSet = beatmapSet;
+
+ metadataLookup.Update(beatmapSet, preferOnlineFetch);
+
+ Assert.That(beatmap.Status, Is.EqualTo(BeatmapOnlineStatus.None));
+ Assert.That(beatmap.OnlineID, Is.EqualTo(-1));
+ }
+
+ [Test]
+ public void TestMetadataLookupForBeatmapWithoutPopulatedIDAndCorrectHash([Values] bool preferOnlineFetch)
+ {
+ var lookupResult = new OnlineBeatmapMetadata
+ {
+ BeatmapID = 654321,
+ BeatmapStatus = BeatmapOnlineStatus.Ranked,
+ MD5Hash = @"deadbeef",
+ };
+
+ var targetMock = preferOnlineFetch ? apiMetadataSourceMock : localCachedMetadataSourceMock;
+ targetMock.Setup(src => src.Available).Returns(true);
+ targetMock.Setup(src => src.TryLookup(It.IsAny(), out lookupResult))
+ .Returns(true);
+
+ var beatmap = new BeatmapInfo
+ {
+ MD5Hash = @"deadbeef"
+ };
+ var beatmapSet = new BeatmapSetInfo(beatmap.Yield());
+ beatmap.BeatmapSet = beatmapSet;
+
+ metadataLookup.Update(beatmapSet, preferOnlineFetch);
+
+ Assert.That(beatmap.Status, Is.EqualTo(BeatmapOnlineStatus.Ranked));
+ Assert.That(beatmap.OnlineID, Is.EqualTo(654321));
+ }
+
+ [Test]
+ public void TestMetadataLookupForBeatmapWithoutPopulatedIDAndIncorrectHash([Values] bool preferOnlineFetch)
+ {
+ var lookupResult = new OnlineBeatmapMetadata
+ {
+ BeatmapID = 654321,
+ BeatmapStatus = BeatmapOnlineStatus.Ranked,
+ MD5Hash = @"cafebabe",
+ };
+
+ var targetMock = preferOnlineFetch ? apiMetadataSourceMock : localCachedMetadataSourceMock;
+ targetMock.Setup(src => src.Available).Returns(true);
+ targetMock.Setup(src => src.TryLookup(It.IsAny(), out lookupResult))
+ .Returns(true);
+
+ var beatmap = new BeatmapInfo
+ {
+ MD5Hash = @"deadbeef"
+ };
+ var beatmapSet = new BeatmapSetInfo(beatmap.Yield());
+ beatmap.BeatmapSet = beatmapSet;
+
+ metadataLookup.Update(beatmapSet, preferOnlineFetch);
+
+ Assert.That(beatmap.Status, Is.EqualTo(BeatmapOnlineStatus.None));
+ Assert.That(beatmap.OnlineID, Is.EqualTo(-1));
+ }
+
+ [Test]
+ public void TestReturnedMetadataHasDifferentHash([Values] bool preferOnlineFetch)
+ {
+ var lookupResult = new OnlineBeatmapMetadata
+ {
+ BeatmapID = 654321,
+ BeatmapStatus = BeatmapOnlineStatus.Ranked,
+ MD5Hash = @"deadbeef"
+ };
+
+ var targetMock = preferOnlineFetch ? apiMetadataSourceMock : localCachedMetadataSourceMock;
+ targetMock.Setup(src => src.Available).Returns(true);
+ targetMock.Setup(src => src.TryLookup(It.IsAny(), out lookupResult))
+ .Returns(true);
+
+ var beatmap = new BeatmapInfo
+ {
+ OnlineID = 654321,
+ MD5Hash = @"cafebabe",
+ };
+ var beatmapSet = new BeatmapSetInfo(beatmap.Yield());
+ beatmap.BeatmapSet = beatmapSet;
+
+ metadataLookup.Update(beatmapSet, preferOnlineFetch);
+
+ Assert.That(beatmap.Status, Is.EqualTo(BeatmapOnlineStatus.None));
+ Assert.That(beatmap.OnlineID, Is.EqualTo(654321));
+ }
+
+ [Test]
+ public void TestPartiallyModifiedSet([Values] bool preferOnlineFetch)
+ {
+ var firstResult = new OnlineBeatmapMetadata
+ {
+ BeatmapID = 654321,
+ BeatmapStatus = BeatmapOnlineStatus.Ranked,
+ BeatmapSetStatus = BeatmapOnlineStatus.Ranked,
+ MD5Hash = @"cafebabe"
+ };
+ var secondResult = new OnlineBeatmapMetadata
+ {
+ BeatmapID = 666666,
+ BeatmapStatus = BeatmapOnlineStatus.Ranked,
+ BeatmapSetStatus = BeatmapOnlineStatus.Ranked,
+ MD5Hash = @"dededede"
+ };
+
+ var targetMock = preferOnlineFetch ? apiMetadataSourceMock : localCachedMetadataSourceMock;
+ targetMock.Setup(src => src.Available).Returns(true);
+ targetMock.Setup(src => src.TryLookup(It.Is(bi => bi.OnlineID == 654321), out firstResult))
+ .Returns(true);
+ targetMock.Setup(src => src.TryLookup(It.Is(bi => bi.OnlineID == 666666), out secondResult))
+ .Returns(true);
+
+ var firstBeatmap = new BeatmapInfo
+ {
+ OnlineID = 654321,
+ MD5Hash = @"cafebabe",
+ };
+ var secondBeatmap = new BeatmapInfo
+ {
+ OnlineID = 666666,
+ MD5Hash = @"deadbeef"
+ };
+ var beatmapSet = new BeatmapSetInfo(new[]
+ {
+ firstBeatmap,
+ secondBeatmap
+ });
+ firstBeatmap.BeatmapSet = beatmapSet;
+ secondBeatmap.BeatmapSet = beatmapSet;
+
+ metadataLookup.Update(beatmapSet, preferOnlineFetch);
+
+ Assert.That(firstBeatmap.Status, Is.EqualTo(BeatmapOnlineStatus.Ranked));
+ Assert.That(firstBeatmap.OnlineID, Is.EqualTo(654321));
+
+ Assert.That(secondBeatmap.Status, Is.EqualTo(BeatmapOnlineStatus.None));
+ Assert.That(secondBeatmap.OnlineID, Is.EqualTo(666666));
+
+ Assert.That(beatmapSet.Status, Is.EqualTo(BeatmapOnlineStatus.None));
+ }
+
+ [Test]
+ public void TestPartiallyMaliciousSet([Values] bool preferOnlineFetch)
+ {
+ var firstResult = new OnlineBeatmapMetadata
+ {
+ BeatmapID = 654321,
+ BeatmapStatus = BeatmapOnlineStatus.Ranked,
+ BeatmapSetStatus = BeatmapOnlineStatus.Ranked,
+ MD5Hash = @"cafebabe"
+ };
+ var secondResult = new OnlineBeatmapMetadata
+ {
+ BeatmapStatus = BeatmapOnlineStatus.Ranked,
+ BeatmapSetStatus = BeatmapOnlineStatus.Ranked,
+ MD5Hash = @"dededede"
+ };
+
+ var targetMock = preferOnlineFetch ? apiMetadataSourceMock : localCachedMetadataSourceMock;
+ targetMock.Setup(src => src.Available).Returns(true);
+ targetMock.Setup(src => src.TryLookup(It.Is(bi => bi.OnlineID == 654321), out firstResult))
+ .Returns(true);
+ targetMock.Setup(src => src.TryLookup(It.Is(bi => bi.OnlineID == 666666), out secondResult))
+ .Returns(true);
+
+ var firstBeatmap = new BeatmapInfo
+ {
+ OnlineID = 654321,
+ MD5Hash = @"cafebabe",
+ };
+ var secondBeatmap = new BeatmapInfo
+ {
+ OnlineID = 666666,
+ MD5Hash = @"deadbeef"
+ };
+ var beatmapSet = new BeatmapSetInfo(new[]
+ {
+ firstBeatmap,
+ secondBeatmap
+ });
+ firstBeatmap.BeatmapSet = beatmapSet;
+ secondBeatmap.BeatmapSet = beatmapSet;
+
+ metadataLookup.Update(beatmapSet, preferOnlineFetch);
+
+ Assert.That(firstBeatmap.Status, Is.EqualTo(BeatmapOnlineStatus.Ranked));
+ Assert.That(firstBeatmap.OnlineID, Is.EqualTo(654321));
+
+ Assert.That(secondBeatmap.Status, Is.EqualTo(BeatmapOnlineStatus.None));
+ Assert.That(secondBeatmap.OnlineID, Is.EqualTo(-1));
+
+ Assert.That(beatmapSet.Status, Is.EqualTo(BeatmapOnlineStatus.None));
+ }
}
}
diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs
index 739a72df08..12d6060351 100644
--- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs
+++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs
@@ -274,10 +274,12 @@ namespace osu.Game.Tests.NonVisual.Filtering
Assert.IsTrue(filterCriteria.OnlineStatus.IsUpperInclusive);
}
- [Test]
- public void TestApplyCreatorQueries()
+ [TestCase("creator")]
+ [TestCase("author")]
+ [TestCase("mapper")]
+ public void TestApplyCreatorQueries(string keyword)
{
- const string query = "beatmap specifically by creator=my_fav";
+ string query = $"beatmap specifically by {keyword}=my_fav";
var filterCriteria = new FilterCriteria();
FilterQueryParser.ApplyQueries(filterCriteria, query);
Assert.AreEqual("beatmap specifically by", filterCriteria.SearchText.Trim());
@@ -452,5 +454,111 @@ namespace osu.Game.Tests.NonVisual.Filtering
return false;
}
}
+
+ private static readonly object[] correct_date_query_examples =
+ {
+ new object[] { "600" },
+ new object[] { "0.5s" },
+ new object[] { "120m" },
+ new object[] { "48h120s" },
+ new object[] { "10y24M" },
+ new object[] { "10y60d120s" },
+ new object[] { "0y0M2d" },
+ new object[] { "1y1M2d" }
+ };
+
+ [Test]
+ [TestCaseSource(nameof(correct_date_query_examples))]
+ public void TestValidDateQueries(string dateQuery)
+ {
+ string query = $"played<{dateQuery} time";
+ var filterCriteria = new FilterCriteria();
+ FilterQueryParser.ApplyQueries(filterCriteria, query);
+ Assert.AreEqual(true, filterCriteria.LastPlayed.HasFilter);
+ }
+
+ private static readonly object[] incorrect_date_query_examples =
+ {
+ new object[] { ".5s" },
+ new object[] { "7m27" },
+ new object[] { "7m7m7m" },
+ new object[] { "5s6m" },
+ new object[] { "7d7y" },
+ new object[] { "0:3:6" },
+ new object[] { "0:3:" },
+ new object[] { "\"three days\"" },
+ new object[] { "0.1y0.1M2d" },
+ new object[] { "0.99y0.99M2d" },
+ new object[] { string.Empty }
+ };
+
+ [Test]
+ [TestCaseSource(nameof(incorrect_date_query_examples))]
+ public void TestInvalidDateQueries(string dateQuery)
+ {
+ string query = $"played<{dateQuery} time";
+ var filterCriteria = new FilterCriteria();
+ FilterQueryParser.ApplyQueries(filterCriteria, query);
+ Assert.AreEqual(false, filterCriteria.LastPlayed.HasFilter);
+ }
+
+ [Test]
+ public void TestGreaterDateQuery()
+ {
+ const string query = "played>50";
+ var filterCriteria = new FilterCriteria();
+ FilterQueryParser.ApplyQueries(filterCriteria, query);
+ Assert.That(filterCriteria.LastPlayed.Max, Is.Not.Null);
+ Assert.That(filterCriteria.LastPlayed.Min, Is.Null);
+ // the parser internally references `DateTimeOffset.Now`, so to not make things too annoying for tests, just assume some tolerance
+ // (irrelevant in proportion to the actual filter proscribed).
+ Assert.That(filterCriteria.LastPlayed.Max, Is.EqualTo(DateTimeOffset.Now.AddDays(-50)).Within(TimeSpan.FromSeconds(5)));
+ }
+
+ [Test]
+ public void TestLowerDateQuery()
+ {
+ const string query = "played<50";
+ var filterCriteria = new FilterCriteria();
+ FilterQueryParser.ApplyQueries(filterCriteria, query);
+ Assert.That(filterCriteria.LastPlayed.Max, Is.Null);
+ Assert.That(filterCriteria.LastPlayed.Min, Is.Not.Null);
+ // the parser internally references `DateTimeOffset.Now`, so to not make things too annoying for tests, just assume some tolerance
+ // (irrelevant in proportion to the actual filter proscribed).
+ Assert.That(filterCriteria.LastPlayed.Min, Is.EqualTo(DateTimeOffset.Now.AddDays(-50)).Within(TimeSpan.FromSeconds(5)));
+ }
+
+ [Test]
+ public void TestBothSidesDateQuery()
+ {
+ const string query = "played>3M played<1y6M";
+ var filterCriteria = new FilterCriteria();
+ FilterQueryParser.ApplyQueries(filterCriteria, query);
+ Assert.That(filterCriteria.LastPlayed.Min, Is.Not.Null);
+ Assert.That(filterCriteria.LastPlayed.Max, Is.Not.Null);
+ // the parser internally references `DateTimeOffset.Now`, so to not make things too annoying for tests, just assume some tolerance
+ // (irrelevant in proportion to the actual filter proscribed).
+ Assert.That(filterCriteria.LastPlayed.Min, Is.EqualTo(DateTimeOffset.Now.AddYears(-1).AddMonths(-6)).Within(TimeSpan.FromSeconds(5)));
+ Assert.That(filterCriteria.LastPlayed.Max, Is.EqualTo(DateTimeOffset.Now.AddMonths(-3)).Within(TimeSpan.FromSeconds(5)));
+ }
+
+ [Test]
+ public void TestEqualDateQuery()
+ {
+ const string query = "played=50";
+ var filterCriteria = new FilterCriteria();
+ FilterQueryParser.ApplyQueries(filterCriteria, query);
+ Assert.AreEqual(false, filterCriteria.LastPlayed.HasFilter);
+ }
+
+ [Test]
+ public void TestOutOfRangeDateQuery()
+ {
+ const string query = "played<10000y";
+ var filterCriteria = new FilterCriteria();
+ FilterQueryParser.ApplyQueries(filterCriteria, query);
+ Assert.AreEqual(true, filterCriteria.LastPlayed.HasFilter);
+ Assert.AreEqual(DateTimeOffset.MinValue.AddMilliseconds(1), filterCriteria.LastPlayed.Min);
+ }
}
}
diff --git a/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs b/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs
index 11f3fe660d..b089144233 100644
--- a/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs
+++ b/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs
@@ -43,13 +43,13 @@ namespace osu.Game.Tests.Rulesets
AddStep("setup provider", () =>
{
- var rulesetSkinProvider = new RulesetSkinProvidingContainer(Ruleset.Value.CreateInstance(), Beatmap.Value.Beatmap, Beatmap.Value.Skin);
-
- rulesetSkinProvider.Add(requester = new SkinRequester());
-
+ requester = new SkinRequester();
requester.OnLoadAsync += () => textureOnLoad = requester.GetTexture("test-image");
- Child = rulesetSkinProvider;
+ Child = new RulesetSkinProvidingContainer(Ruleset.Value.CreateInstance(), Beatmap.Value.Beatmap, Beatmap.Value.Skin)
+ {
+ Child = requester
+ };
});
AddAssert("requester got correct initial texture", () => textureOnLoad != null);
diff --git a/osu.Game.Tests/Visual/Menus/TestSceneToolbarUserButton.cs b/osu.Game.Tests/Visual/Menus/TestSceneToolbarUserButton.cs
index f0506ed35c..69fedf4a3a 100644
--- a/osu.Game.Tests/Visual/Menus/TestSceneToolbarUserButton.cs
+++ b/osu.Game.Tests/Visual/Menus/TestSceneToolbarUserButton.cs
@@ -2,12 +2,17 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
+using osu.Framework.Testing;
using osu.Game.Online.API;
+using osu.Game.Online.Solo;
using osu.Game.Overlays.Toolbar;
+using osu.Game.Scoring;
+using osu.Game.Users;
using osuTK;
using osuTK.Graphics;
@@ -87,5 +92,91 @@ namespace osu.Game.Tests.Visual.Menus
AddStep($"Change state to {state}", () => dummyAPI.SetState(state));
}
}
+
+ [Test]
+ public void TestTransientUserStatisticsDisplay()
+ {
+ AddStep("Log in", () => dummyAPI.Login("wang", "jang"));
+ AddStep("Gain", () =>
+ {
+ var transientUpdateDisplay = this.ChildrenOfType().Single();
+ transientUpdateDisplay.LatestUpdate.Value = new SoloStatisticsUpdate(
+ new ScoreInfo(),
+ new UserStatistics
+ {
+ GlobalRank = 123_456,
+ PP = 1234
+ },
+ new UserStatistics
+ {
+ GlobalRank = 111_111,
+ PP = 1357
+ });
+ });
+ AddStep("Loss", () =>
+ {
+ var transientUpdateDisplay = this.ChildrenOfType().Single();
+ transientUpdateDisplay.LatestUpdate.Value = new SoloStatisticsUpdate(
+ new ScoreInfo(),
+ new UserStatistics
+ {
+ GlobalRank = 111_111,
+ PP = 1357
+ },
+ new UserStatistics
+ {
+ GlobalRank = 123_456,
+ PP = 1234
+ });
+ });
+ AddStep("No change", () =>
+ {
+ var transientUpdateDisplay = this.ChildrenOfType().Single();
+ transientUpdateDisplay.LatestUpdate.Value = new SoloStatisticsUpdate(
+ new ScoreInfo(),
+ new UserStatistics
+ {
+ GlobalRank = 111_111,
+ PP = 1357
+ },
+ new UserStatistics
+ {
+ GlobalRank = 111_111,
+ PP = 1357
+ });
+ });
+ AddStep("Was null", () =>
+ {
+ var transientUpdateDisplay = this.ChildrenOfType().Single();
+ transientUpdateDisplay.LatestUpdate.Value = new SoloStatisticsUpdate(
+ new ScoreInfo(),
+ new UserStatistics
+ {
+ GlobalRank = null,
+ PP = null
+ },
+ new UserStatistics
+ {
+ GlobalRank = 111_111,
+ PP = 1357
+ });
+ });
+ AddStep("Became null", () =>
+ {
+ var transientUpdateDisplay = this.ChildrenOfType().Single();
+ transientUpdateDisplay.LatestUpdate.Value = new SoloStatisticsUpdate(
+ new ScoreInfo(),
+ new UserStatistics
+ {
+ GlobalRank = 111_111,
+ PP = 1357
+ },
+ new UserStatistics
+ {
+ GlobalRank = null,
+ PP = null
+ });
+ });
+ }
}
}
diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs
index f59fbc75ac..8ff4fd5ecf 100644
--- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs
+++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs
@@ -986,6 +986,29 @@ namespace osu.Game.Tests.Visual.Navigation
}
}
+ [Test]
+ public void TestPresentBeatmapAfterDeletion()
+ {
+ BeatmapSetInfo beatmap = null;
+
+ Screens.Select.SongSelect songSelect = null;
+ PushAndConfirm(() => songSelect = new TestPlaySongSelect());
+ AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded);
+
+ AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely());
+ AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault);
+
+ AddStep("delete selected beatmap", () =>
+ {
+ beatmap = Game.Beatmap.Value.BeatmapSetInfo;
+ Game.BeatmapManager.Delete(Game.Beatmap.Value.BeatmapSetInfo);
+ });
+
+ AddUntilStep("nothing selected", () => Game.Beatmap.IsDefault);
+ AddStep("present deleted beatmap", () => Game.PresentBeatmap(beatmap));
+ AddAssert("still nothing selected", () => Game.Beatmap.IsDefault);
+ }
+
private Func playToResults()
{
var player = playToCompletion();
diff --git a/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs
index 3d8781d902..fd3552f675 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs
@@ -170,6 +170,24 @@ namespace osu.Game.Tests.Visual.Online
});
}
+ [Test]
+ public void TestPostAsOwner()
+ {
+ setUpCommentsResponse(getExampleComments());
+ AddStep("show comments", () => commentsContainer.ShowComments(CommentableType.Beatmapset, 123));
+
+ setUpPostResponse(true);
+ AddStep("enter text", () => editorTextBox.Current.Value = "comm");
+ AddStep("submit", () => commentsContainer.ChildrenOfType().Single().ChildrenOfType().First().TriggerClick());
+
+ AddUntilStep("comment sent", () =>
+ {
+ string writtenText = editorTextBox.Current.Value;
+ var comment = commentsContainer.ChildrenOfType().LastOrDefault();
+ return comment != null && comment.ChildrenOfType().Any(y => y.Text == writtenText) && comment.ChildrenOfType().Any(y => y.Text == "MAPPER");
+ });
+ }
+
private void setUpCommentsResponse(CommentBundle commentBundle)
=> AddStep("set up response", () =>
{
@@ -183,7 +201,7 @@ namespace osu.Game.Tests.Visual.Online
};
});
- private void setUpPostResponse()
+ private void setUpPostResponse(bool asOwner = false)
=> AddStep("set up response", () =>
{
dummyAPI.HandleRequest = request =>
@@ -191,7 +209,7 @@ namespace osu.Game.Tests.Visual.Online
if (!(request is CommentPostRequest req))
return false;
- req.TriggerSuccess(new CommentBundle
+ var bundle = new CommentBundle
{
Comments = new List
{
@@ -202,9 +220,26 @@ namespace osu.Game.Tests.Visual.Online
LegacyName = "FirstUser",
CreatedAt = DateTimeOffset.Now,
VotesCount = 98,
+ CommentableId = 2001,
+ CommentableType = "test",
}
}
- });
+ };
+
+ if (asOwner)
+ {
+ bundle.Comments[0].UserId = 1001;
+ bundle.Comments[0].User = new APIUser { Id = 1001, Username = "FirstUser" };
+ bundle.CommentableMeta.Add(new CommentableMeta
+ {
+ Id = 2001,
+ OwnerId = 1001,
+ OwnerTitle = "MAPPER",
+ Type = "test",
+ });
+ }
+
+ req.TriggerSuccess(bundle);
return true;
};
});
diff --git a/osu.Game.Tests/Visual/Online/TestSceneDrawableComment.cs b/osu.Game.Tests/Visual/Online/TestSceneDrawableComment.cs
index 5e83dd4fb3..6f09e4c1f6 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneDrawableComment.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneDrawableComment.cs
@@ -4,62 +4,66 @@
#nullable disable
using System;
-using NUnit.Framework;
-using osu.Framework.Allocation;
+using System.Linq;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Shapes;
+using osu.Game.Graphics.Containers;
using osu.Game.Online.API.Requests.Responses;
-using osu.Game.Overlays;
using osu.Game.Overlays.Comments;
+using osu.Game.Tests.Visual.UserInterface;
namespace osu.Game.Tests.Visual.Online
{
- public partial class TestSceneDrawableComment : OsuTestScene
+ public partial class TestSceneDrawableComment : ThemeComparisonTestScene
{
- [Cached]
- private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
-
- private Container container;
-
- [SetUp]
- public void SetUp() => Schedule(() =>
+ public TestSceneDrawableComment()
+ : base(false)
{
- 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.Pinned = description == "Pinned";
- comment.Message = text;
- container.Add(new DrawableComment(comment));
- });
}
- private static readonly Comment comment = new Comment
+ protected override Drawable CreateContent() => new OsuScrollContainer(Direction.Vertical)
{
- Id = 1,
- LegacyName = "Test User",
- CreatedAt = DateTimeOffset.Now,
- VotesCount = 0,
+ RelativeSizeAxes = Axes.Both,
+ Child = new FillFlowContainer
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Direction = FillDirection.Vertical,
+ ChildrenEnumerable = comments.Select(info =>
+ {
+ var comment = new Comment
+ {
+ Id = 1,
+ UserId = 1000,
+ User = new APIUser { Id = 1000, Username = "Someone" },
+ CreatedAt = DateTimeOffset.Now,
+ VotesCount = 0,
+ Pinned = info[0] == "Pinned",
+ Message = info[1],
+ CommentableId = 2001,
+ CommentableType = "test"
+ };
+
+ return new[]
+ {
+ new DrawableComment(comment, Array.Empty()),
+ new DrawableComment(comment, new[]
+ {
+ new CommentableMeta
+ {
+ Id = 2001,
+ OwnerId = comment.UserId,
+ OwnerTitle = "MAPPER",
+ Type = "test",
+ },
+ new CommentableMeta { Title = "Other Meta" },
+ }),
+ };
+ }).SelectMany(c => c)
+ }
};
- private static object[] comments =
+ private static readonly string[][] comments =
{
new[] { "Plain", "This is plain comment" },
new[] { "Pinned", "This is pinned comment" },
diff --git a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs
index 2bfbf76c10..33f4d577bd 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs
@@ -154,6 +154,19 @@ namespace osu.Game.Tests.Visual.Online
});
}
+ [Test]
+ public void TestUnrankedPP()
+ {
+ AddStep("Load scores with unranked PP", () =>
+ {
+ var allScores = createScores();
+ allScores.Scores[0].Ranked = false;
+ allScores.UserScore = createUserBest();
+ allScores.UserScore.Score.Ranked = false;
+ scoresContainer.Scores = allScores;
+ });
+ }
+
private ulong onlineID = 1;
private APIScoresCollection createScores()
@@ -184,6 +197,7 @@ namespace osu.Game.Tests.Visual.Online
MaxCombo = 1234,
TotalScore = 1234567890,
Accuracy = 1,
+ Ranked = true,
},
new SoloScoreInfo
{
@@ -206,6 +220,7 @@ namespace osu.Game.Tests.Visual.Online
MaxCombo = 1234,
TotalScore = 1234789,
Accuracy = 0.9997,
+ Ranked = true,
},
new SoloScoreInfo
{
@@ -227,6 +242,7 @@ namespace osu.Game.Tests.Visual.Online
MaxCombo = 1234,
TotalScore = 12345678,
Accuracy = 0.9854,
+ Ranked = true,
},
new SoloScoreInfo
{
@@ -247,6 +263,7 @@ namespace osu.Game.Tests.Visual.Online
MaxCombo = 1234,
TotalScore = 1234567,
Accuracy = 0.8765,
+ Ranked = true,
},
new SoloScoreInfo
{
@@ -263,6 +280,7 @@ namespace osu.Game.Tests.Visual.Online
MaxCombo = 1234,
TotalScore = 123456,
Accuracy = 0.6543,
+ Ranked = true,
},
}
};
@@ -309,6 +327,7 @@ namespace osu.Game.Tests.Visual.Online
MaxCombo = 1234,
TotalScore = 123456,
Accuracy = 0.6543,
+ Ranked = true,
},
Position = 1337,
};
diff --git a/osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs b/osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs
index 3607b37c7e..19121b7f58 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs
@@ -35,8 +35,6 @@ namespace osu.Game.Tests.Visual.Online
private Action? handleGetUsersRequest;
private Action? handleGetUserRequest;
- private IDisposable? subscription;
-
private readonly Dictionary<(int userId, string rulesetName), UserStatistics> serverSideStatistics = new Dictionary<(int userId, string rulesetName), UserStatistics>();
[SetUpSteps]
@@ -252,26 +250,6 @@ namespace osu.Game.Tests.Visual.Online
AddAssert("values after are correct", () => update!.After.TotalScore, () => Is.EqualTo(6_000_000));
}
- [Test]
- public void TestStatisticsUpdateNotFiredAfterSubscriptionDisposal()
- {
- int userId = getUserId();
- setUpUser(userId);
-
- long scoreId = getScoreId();
- var ruleset = new OsuRuleset().RulesetInfo;
-
- SoloStatisticsUpdate? update = null;
- registerForUpdates(scoreId, ruleset, receivedUpdate => update = receivedUpdate);
- AddStep("unsubscribe", () => subscription!.Dispose());
-
- feignScoreProcessing(userId, ruleset, 5_000_000);
-
- AddStep("signal score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(userId, scoreId));
- AddWaitStep("wait a bit", 5);
- AddAssert("update not received", () => update == null);
- }
-
[Test]
public void TestGlobalStatisticsUpdatedAfterRegistrationAddedAndScoreProcessed()
{
@@ -312,13 +290,20 @@ namespace osu.Game.Tests.Visual.Online
}
private void registerForUpdates(long scoreId, RulesetInfo rulesetInfo, Action onUpdateReady) =>
- AddStep("register for updates", () => subscription = watcher.RegisterForStatisticsUpdateAfter(
- new ScoreInfo(Beatmap.Value.BeatmapInfo, new OsuRuleset().RulesetInfo, new RealmUser())
+ AddStep("register for updates", () =>
+ {
+ watcher.RegisterForStatisticsUpdateAfter(
+ new ScoreInfo(Beatmap.Value.BeatmapInfo, new OsuRuleset().RulesetInfo, new RealmUser())
+ {
+ Ruleset = rulesetInfo,
+ OnlineID = scoreId
+ });
+ watcher.LatestUpdate.BindValueChanged(update =>
{
- Ruleset = rulesetInfo,
- OnlineID = scoreId
- },
- onUpdateReady));
+ if (update.NewValue?.Score.OnlineID == scoreId)
+ onUpdateReady.Invoke(update.NewValue);
+ });
+ });
private void feignScoreProcessing(int userId, RulesetInfo rulesetInfo, long newTotalScore)
=> AddStep("feign score processing", () => serverSideStatistics[(userId, rulesetInfo.ShortName)] = new UserStatistics { TotalScore = newTotalScore });
diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileScores.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileScores.cs
index 5249e8694d..56e4348b65 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileScores.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileScores.cs
@@ -40,7 +40,8 @@ namespace osu.Game.Tests.Visual.Online
new APIMod { Acronym = new OsuModHardRock().Acronym },
new APIMod { Acronym = new OsuModDoubleTime().Acronym },
},
- Accuracy = 0.9813
+ Accuracy = 0.9813,
+ Ranked = true,
};
var secondScore = new SoloScoreInfo
@@ -62,7 +63,8 @@ namespace osu.Game.Tests.Visual.Online
new APIMod { Acronym = new OsuModHardRock().Acronym },
new APIMod { Acronym = new OsuModDoubleTime().Acronym },
},
- Accuracy = 0.998546
+ Accuracy = 0.998546,
+ Ranked = true,
};
var thirdScore = new SoloScoreInfo
@@ -79,7 +81,8 @@ namespace osu.Game.Tests.Visual.Online
DifficultyName = "Insane"
},
EndedAt = DateTimeOffset.Now,
- Accuracy = 0.9726
+ Accuracy = 0.9726,
+ Ranked = true,
};
var noPPScore = new SoloScoreInfo
@@ -95,7 +98,26 @@ namespace osu.Game.Tests.Visual.Online
DifficultyName = "[4K] Cataclysmic Hypernova"
},
EndedAt = DateTimeOffset.Now,
- Accuracy = 0.55879
+ Accuracy = 0.55879,
+ Ranked = true,
+ };
+
+ var lovedScore = new SoloScoreInfo
+ {
+ Rank = ScoreRank.B,
+ Beatmap = new APIBeatmap
+ {
+ BeatmapSet = new APIBeatmapSet
+ {
+ Title = "C18H27NO3(extend)",
+ Artist = "Team Grimoire",
+ },
+ DifficultyName = "[4K] Cataclysmic Hypernova",
+ Status = BeatmapOnlineStatus.Loved,
+ },
+ EndedAt = DateTimeOffset.Now,
+ Accuracy = 0.55879,
+ Ranked = true,
};
var unprocessedPPScore = new SoloScoreInfo
@@ -112,7 +134,26 @@ namespace osu.Game.Tests.Visual.Online
Status = BeatmapOnlineStatus.Ranked,
},
EndedAt = DateTimeOffset.Now,
- Accuracy = 0.55879
+ Accuracy = 0.55879,
+ Ranked = true,
+ };
+
+ var unrankedPPScore = new SoloScoreInfo
+ {
+ Rank = ScoreRank.B,
+ Beatmap = new APIBeatmap
+ {
+ BeatmapSet = new APIBeatmapSet
+ {
+ Title = "C18H27NO3(extend)",
+ Artist = "Team Grimoire",
+ },
+ DifficultyName = "[4K] Cataclysmic Hypernova",
+ Status = BeatmapOnlineStatus.Ranked,
+ },
+ EndedAt = DateTimeOffset.Now,
+ Accuracy = 0.55879,
+ Ranked = false,
};
Add(new FillFlowContainer
@@ -128,7 +169,9 @@ namespace osu.Game.Tests.Visual.Online
new ColourProvidedContainer(OverlayColourScheme.Green, new DrawableProfileScore(firstScore)),
new ColourProvidedContainer(OverlayColourScheme.Green, new DrawableProfileScore(secondScore)),
new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileScore(noPPScore)),
+ new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileScore(lovedScore)),
new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileScore(unprocessedPPScore)),
+ new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileScore(unrankedPPScore)),
new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileWeightedScore(firstScore, 0.97)),
new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileWeightedScore(secondScore, 0.85)),
new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileWeightedScore(thirdScore, 0.66)),
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuDropdown.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuDropdown.cs
index 1678890b73..63f7a2f2cc 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuDropdown.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuDropdown.cs
@@ -9,7 +9,6 @@ using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events;
using osu.Framework.Input.States;
using osu.Framework.Testing;
-using osu.Game.Beatmaps;
using osu.Game.Graphics.UserInterface;
using osu.Game.Input.Bindings;
@@ -18,13 +17,22 @@ namespace osu.Game.Tests.Visual.UserInterface
public partial class TestSceneOsuDropdown : ThemeComparisonTestScene
{
protected override Drawable CreateContent() =>
- new OsuEnumDropdown
+ new OsuEnumDropdown
{
Anchor = Anchor.Centre,
Origin = Anchor.TopCentre,
Width = 150
};
+ private enum TestEnum
+ {
+ [System.ComponentModel.Description("Option")]
+ Option,
+
+ [System.ComponentModel.Description("Really lonnnnnnng option")]
+ ReallyLongOption,
+ }
+
[Test]
// todo: this can be written much better if ThemeComparisonTestScene has a manual input manager
public void TestBackAction()
@@ -43,7 +51,7 @@ namespace osu.Game.Tests.Visual.UserInterface
AddStep("press back", () => dropdown().OnPressed(new KeyBindingPressEvent(new InputState(), GlobalAction.Back)));
AddAssert("closed", () => dropdown().ChildrenOfType
-
+
diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/300076680-5cbe0121-ed68-414f-9ddc-dd993ac97e62.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/300076680-5cbe0121-ed68-414f-9ddc-dd993ac97e62.png
index 21f5f0f3a0..9287a71040 100644
Binary files a/osu.iOS/Assets.xcassets/AppIcon.appiconset/300076680-5cbe0121-ed68-414f-9ddc-dd993ac97e62.png and b/osu.iOS/Assets.xcassets/AppIcon.appiconset/300076680-5cbe0121-ed68-414f-9ddc-dd993ac97e62.png differ