Merge remote-tracking branch 'upstream/master' into lead-in-fixes

This commit is contained in:
Dean Herbert
2019-11-15 14:03:51 +09:00
1312 changed files with 52721 additions and 15918 deletions

View File

@ -0,0 +1,154 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
using Microsoft.EntityFrameworkCore.Internal;
using NUnit.Framework;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit;
namespace osu.Game.Tests.Beatmaps
{
[TestFixture]
public class EditorBeatmapTest
{
/// <summary>
/// Tests that the addition event is correctly invoked after a hitobject is added.
/// </summary>
[Test]
public void TestHitObjectAddEvent()
{
var editorBeatmap = new EditorBeatmap<OsuHitObject>(new OsuBeatmap());
HitObject addedObject = null;
editorBeatmap.HitObjectAdded += h => addedObject = h;
var hitCircle = new HitCircle();
editorBeatmap.Add(hitCircle);
Assert.That(addedObject, Is.EqualTo(hitCircle));
}
/// <summary>
/// Tests that the removal event is correctly invoked after a hitobject is removed.
/// </summary>
[Test]
public void HitObjectRemoveEvent()
{
var hitCircle = new HitCircle();
var editorBeatmap = new EditorBeatmap<OsuHitObject>(new OsuBeatmap { HitObjects = { hitCircle } });
HitObject removedObject = null;
editorBeatmap.HitObjectRemoved += h => removedObject = h;
editorBeatmap.Remove(hitCircle);
Assert.That(removedObject, Is.EqualTo(hitCircle));
}
/// <summary>
/// Tests that the changed event is correctly invoked after the start time of a hitobject is changed.
/// This tests for hitobjects which were already present before the editor beatmap was constructed.
/// </summary>
[Test]
public void TestInitialHitObjectStartTimeChangeEvent()
{
var hitCircle = new HitCircle();
var editorBeatmap = new EditorBeatmap<OsuHitObject>(new OsuBeatmap { HitObjects = { hitCircle } });
HitObject changedObject = null;
editorBeatmap.StartTimeChanged += h => changedObject = h;
hitCircle.StartTime = 1000;
Assert.That(changedObject, Is.EqualTo(hitCircle));
}
/// <summary>
/// Tests that the changed event is correctly invoked after the start time of a hitobject is changed.
/// This tests for hitobjects which were added to an existing editor beatmap.
/// </summary>
[Test]
public void TestAddedHitObjectStartTimeChangeEvent()
{
var editorBeatmap = new EditorBeatmap<OsuHitObject>(new OsuBeatmap());
HitObject changedObject = null;
editorBeatmap.StartTimeChanged += h => changedObject = h;
var hitCircle = new HitCircle();
editorBeatmap.Add(hitCircle);
Assert.That(changedObject, Is.Null);
hitCircle.StartTime = 1000;
Assert.That(changedObject, Is.EqualTo(hitCircle));
}
/// <summary>
/// Tests that the channged event is not invoked after a hitobject is removed from the beatmap/
/// </summary>
[Test]
public void TestRemovedHitObjectStartTimeChangeEvent()
{
var hitCircle = new HitCircle();
var editorBeatmap = new EditorBeatmap<OsuHitObject>(new OsuBeatmap { HitObjects = { hitCircle } });
HitObject changedObject = null;
editorBeatmap.StartTimeChanged += h => changedObject = h;
editorBeatmap.Remove(hitCircle);
Assert.That(changedObject, Is.Null);
hitCircle.StartTime = 1000;
Assert.That(changedObject, Is.Null);
}
/// <summary>
/// Tests that an added hitobject is correctly inserted to preserve the sorting order of the beatmap.
/// </summary>
[Test]
public void TestAddHitObjectInMiddle()
{
var editorBeatmap = new EditorBeatmap<OsuHitObject>(new OsuBeatmap
{
HitObjects =
{
new HitCircle(),
new HitCircle { StartTime = 1000 },
new HitCircle { StartTime = 1000 },
new HitCircle { StartTime = 2000 },
}
});
var hitCircle = new HitCircle { StartTime = 1000 };
editorBeatmap.Add(hitCircle);
Assert.That(editorBeatmap.HitObjects.Count(h => h == hitCircle), Is.EqualTo(1));
Assert.That(editorBeatmap.HitObjects.IndexOf(hitCircle), Is.EqualTo(3));
}
/// <summary>
/// Tests that the beatmap remains correctly sorted after the start time of a hitobject is changed.
/// </summary>
[Test]
public void TestResortWhenStartTimeChanged()
{
var hitCircle = new HitCircle { StartTime = 1000 };
var editorBeatmap = new EditorBeatmap<OsuHitObject>(new OsuBeatmap
{
HitObjects =
{
new HitCircle(),
new HitCircle { StartTime = 1000 },
new HitCircle { StartTime = 1000 },
hitCircle,
new HitCircle { StartTime = 2000 },
}
});
hitCircle.StartTime = 0;
Assert.That(editorBeatmap.HitObjects.Count(h => h == hitCircle), Is.EqualTo(1));
Assert.That(editorBeatmap.HitObjects.IndexOf(hitCircle), Is.EqualTo(1));
}
}
}

View File

@ -13,6 +13,7 @@ using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Beatmaps.Formats;
using osu.Game.Beatmaps.Timing;
using osu.Game.IO;
using osu.Game.Rulesets.Catch.Beatmaps;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
@ -30,13 +31,9 @@ namespace osu.Game.Tests.Beatmaps.Formats
public void TestDecodeBeatmapVersion()
{
using (var resStream = TestResources.OpenResource("beatmap-version.osu"))
using (var stream = new StreamReader(resStream))
using (var stream = new LineBufferedReader(resStream))
{
var decoder = Decoder.GetDecoder<Beatmap>(stream);
stream.BaseStream.Position = 0;
stream.DiscardBufferedData();
var working = new TestWorkingBeatmap(decoder.Decode(stream));
Assert.AreEqual(6, working.BeatmapInfo.BeatmapVersion);
@ -51,7 +48,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
using (var resStream = TestResources.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
using (var stream = new StreamReader(resStream))
using (var stream = new LineBufferedReader(resStream))
{
var beatmap = decoder.Decode(stream);
var beatmapInfo = beatmap.BeatmapInfo;
@ -75,7 +72,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
var decoder = new LegacyBeatmapDecoder();
using (var resStream = TestResources.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
using (var stream = new StreamReader(resStream))
using (var stream = new LineBufferedReader(resStream))
{
var beatmapInfo = decoder.Decode(stream).BeatmapInfo;
@ -101,7 +98,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
var decoder = new LegacyBeatmapDecoder();
using (var resStream = TestResources.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
using (var stream = new StreamReader(resStream))
using (var stream = new LineBufferedReader(resStream))
{
var beatmap = decoder.Decode(stream);
var beatmapInfo = beatmap.BeatmapInfo;
@ -126,7 +123,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
var decoder = new LegacyBeatmapDecoder();
using (var resStream = TestResources.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
using (var stream = new StreamReader(resStream))
using (var stream = new LineBufferedReader(resStream))
{
var difficulty = decoder.Decode(stream).BeatmapInfo.BaseDifficulty;
@ -145,7 +142,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
using (var resStream = TestResources.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
using (var stream = new StreamReader(resStream))
using (var stream = new LineBufferedReader(resStream))
{
var beatmap = decoder.Decode(stream);
var metadata = beatmap.Metadata;
@ -164,15 +161,15 @@ namespace osu.Game.Tests.Beatmaps.Formats
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
using (var resStream = TestResources.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
using (var stream = new StreamReader(resStream))
using (var stream = new LineBufferedReader(resStream))
{
var beatmap = decoder.Decode(stream);
var controlPoints = beatmap.ControlPointInfo;
Assert.AreEqual(4, controlPoints.TimingPoints.Count);
Assert.AreEqual(42, controlPoints.DifficultyPoints.Count);
Assert.AreEqual(42, controlPoints.SamplePoints.Count);
Assert.AreEqual(42, controlPoints.EffectPoints.Count);
Assert.AreEqual(5, controlPoints.DifficultyPoints.Count);
Assert.AreEqual(34, controlPoints.SamplePoints.Count);
Assert.AreEqual(8, controlPoints.EffectPoints.Count);
var timingPoint = controlPoints.TimingPointAt(0);
Assert.AreEqual(956, timingPoint.Time);
@ -194,7 +191,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.AreEqual(1.0, difficultyPoint.SpeedMultiplier);
difficultyPoint = controlPoints.DifficultyPointAt(48428);
Assert.AreEqual(48428, difficultyPoint.Time);
Assert.AreEqual(0, difficultyPoint.Time);
Assert.AreEqual(1.0, difficultyPoint.SpeedMultiplier);
difficultyPoint = controlPoints.DifficultyPointAt(116999);
@ -227,7 +224,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.IsFalse(effectPoint.OmitFirstBarLine);
effectPoint = controlPoints.EffectPointAt(119637);
Assert.AreEqual(119637, effectPoint.Time);
Assert.AreEqual(95901, effectPoint.Time);
Assert.IsFalse(effectPoint.KiaiMode);
Assert.IsFalse(effectPoint.OmitFirstBarLine);
}
@ -239,7 +236,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
using (var resStream = TestResources.OpenResource("overlapping-control-points.osu"))
using (var stream = new StreamReader(resStream))
using (var stream = new LineBufferedReader(resStream))
{
var controlPoints = decoder.Decode(stream).ControlPointInfo;
@ -265,13 +262,28 @@ namespace osu.Game.Tests.Beatmaps.Formats
}
}
[Test]
public void TestTimingPointResetsSpeedMultiplier()
{
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
using (var resStream = TestResources.OpenResource("timingpoint-speedmultiplier-reset.osu"))
using (var stream = new LineBufferedReader(resStream))
{
var controlPoints = decoder.Decode(stream).ControlPointInfo;
Assert.That(controlPoints.DifficultyPointAt(0).SpeedMultiplier, Is.EqualTo(0.5).Within(0.1));
Assert.That(controlPoints.DifficultyPointAt(2000).SpeedMultiplier, Is.EqualTo(1).Within(0.1));
}
}
[Test]
public void TestDecodeBeatmapColours()
{
var decoder = new LegacySkinDecoder();
using (var resStream = TestResources.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
using (var stream = new StreamReader(resStream))
using (var stream = new LineBufferedReader(resStream))
{
var comboColors = decoder.Decode(stream).ComboColours;
@ -297,7 +309,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
var decoder = new LegacyBeatmapDecoder();
using (var resStream = TestResources.OpenResource("hitobject-combo-offset.osu"))
using (var stream = new StreamReader(resStream))
using (var stream = new LineBufferedReader(resStream))
{
var beatmap = decoder.Decode(stream);
@ -320,7 +332,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
var decoder = new LegacyBeatmapDecoder();
using (var resStream = TestResources.OpenResource("hitobject-combo-offset.osu"))
using (var stream = new StreamReader(resStream))
using (var stream = new LineBufferedReader(resStream))
{
var beatmap = decoder.Decode(stream);
@ -343,7 +355,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
using (var resStream = TestResources.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
using (var stream = new StreamReader(resStream))
using (var stream = new LineBufferedReader(resStream))
{
var hitObjects = decoder.Decode(stream).HitObjects;
@ -354,14 +366,31 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.IsNotNull(curveData);
Assert.AreEqual(new Vector2(192, 168), positionData.Position);
Assert.AreEqual(956, hitObjects[0].StartTime);
Assert.IsTrue(hitObjects[0].Samples.Any(s => s.Name == SampleInfo.HIT_NORMAL));
Assert.IsTrue(hitObjects[0].Samples.Any(s => s.Name == HitSampleInfo.HIT_NORMAL));
positionData = hitObjects[1] as IHasPosition;
Assert.IsNotNull(positionData);
Assert.AreEqual(new Vector2(304, 56), positionData.Position);
Assert.AreEqual(1285, hitObjects[1].StartTime);
Assert.IsTrue(hitObjects[1].Samples.Any(s => s.Name == SampleInfo.HIT_CLAP));
Assert.IsTrue(hitObjects[1].Samples.Any(s => s.Name == HitSampleInfo.HIT_CLAP));
}
}
[Test]
public void TestDecodeControlPointDifficultyChange()
{
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
using (var resStream = TestResources.OpenResource("controlpoint-difficulty-multiplier.osu"))
using (var stream = new LineBufferedReader(resStream))
{
var controlPointInfo = decoder.Decode(stream).ControlPointInfo;
Assert.That(controlPointInfo.DifficultyPointAt(5).SpeedMultiplier, Is.EqualTo(1));
Assert.That(controlPointInfo.DifficultyPointAt(1000).SpeedMultiplier, Is.EqualTo(10));
Assert.That(controlPointInfo.DifficultyPointAt(2000).SpeedMultiplier, Is.EqualTo(1.8518518518518519d));
Assert.That(controlPointInfo.DifficultyPointAt(3000).SpeedMultiplier, Is.EqualTo(0.5));
}
}
@ -371,7 +400,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
using (var resStream = TestResources.OpenResource("controlpoint-custom-samplebank.osu"))
using (var stream = new StreamReader(resStream))
using (var stream = new LineBufferedReader(resStream))
{
var hitObjects = decoder.Decode(stream).HitObjects;
@ -384,7 +413,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.AreEqual("soft-hitnormal8", getTestableSampleInfo(hitObjects[4]).LookupNames.First());
}
SampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]);
HitSampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]);
}
[Test]
@ -393,7 +422,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
using (var resStream = TestResources.OpenResource("hitobject-custom-samplebank.osu"))
using (var stream = new StreamReader(resStream))
using (var stream = new LineBufferedReader(resStream))
{
var hitObjects = decoder.Decode(stream).HitObjects;
@ -402,7 +431,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.AreEqual("normal-hitnormal3", getTestableSampleInfo(hitObjects[2]).LookupNames.First());
}
SampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]);
HitSampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]);
}
[Test]
@ -411,7 +440,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
using (var resStream = TestResources.OpenResource("hitobject-file-samples.osu"))
using (var stream = new StreamReader(resStream))
using (var stream = new LineBufferedReader(resStream))
{
var hitObjects = decoder.Decode(stream).HitObjects;
@ -422,7 +451,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.AreEqual(70, getTestableSampleInfo(hitObjects[3]).Volume);
}
SampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]);
HitSampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]);
}
[Test]
@ -431,41 +460,41 @@ namespace osu.Game.Tests.Beatmaps.Formats
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
using (var resStream = TestResources.OpenResource("slider-samples.osu"))
using (var stream = new StreamReader(resStream))
using (var stream = new LineBufferedReader(resStream))
{
var hitObjects = decoder.Decode(stream).HitObjects;
var slider1 = (ConvertSlider)hitObjects[0];
Assert.AreEqual(1, slider1.NodeSamples[0].Count);
Assert.AreEqual(SampleInfo.HIT_NORMAL, slider1.NodeSamples[0][0].Name);
Assert.AreEqual(HitSampleInfo.HIT_NORMAL, slider1.NodeSamples[0][0].Name);
Assert.AreEqual(1, slider1.NodeSamples[1].Count);
Assert.AreEqual(SampleInfo.HIT_NORMAL, slider1.NodeSamples[1][0].Name);
Assert.AreEqual(HitSampleInfo.HIT_NORMAL, slider1.NodeSamples[1][0].Name);
Assert.AreEqual(1, slider1.NodeSamples[2].Count);
Assert.AreEqual(SampleInfo.HIT_NORMAL, slider1.NodeSamples[2][0].Name);
Assert.AreEqual(HitSampleInfo.HIT_NORMAL, slider1.NodeSamples[2][0].Name);
var slider2 = (ConvertSlider)hitObjects[1];
Assert.AreEqual(2, slider2.NodeSamples[0].Count);
Assert.AreEqual(SampleInfo.HIT_NORMAL, slider2.NodeSamples[0][0].Name);
Assert.AreEqual(SampleInfo.HIT_CLAP, slider2.NodeSamples[0][1].Name);
Assert.AreEqual(HitSampleInfo.HIT_NORMAL, slider2.NodeSamples[0][0].Name);
Assert.AreEqual(HitSampleInfo.HIT_CLAP, slider2.NodeSamples[0][1].Name);
Assert.AreEqual(2, slider2.NodeSamples[1].Count);
Assert.AreEqual(SampleInfo.HIT_NORMAL, slider2.NodeSamples[1][0].Name);
Assert.AreEqual(SampleInfo.HIT_CLAP, slider2.NodeSamples[1][1].Name);
Assert.AreEqual(HitSampleInfo.HIT_NORMAL, slider2.NodeSamples[1][0].Name);
Assert.AreEqual(HitSampleInfo.HIT_CLAP, slider2.NodeSamples[1][1].Name);
Assert.AreEqual(2, slider2.NodeSamples[2].Count);
Assert.AreEqual(SampleInfo.HIT_NORMAL, slider2.NodeSamples[2][0].Name);
Assert.AreEqual(SampleInfo.HIT_CLAP, slider2.NodeSamples[2][1].Name);
Assert.AreEqual(HitSampleInfo.HIT_NORMAL, slider2.NodeSamples[2][0].Name);
Assert.AreEqual(HitSampleInfo.HIT_CLAP, slider2.NodeSamples[2][1].Name);
var slider3 = (ConvertSlider)hitObjects[2];
Assert.AreEqual(2, slider3.NodeSamples[0].Count);
Assert.AreEqual(SampleInfo.HIT_NORMAL, slider3.NodeSamples[0][0].Name);
Assert.AreEqual(SampleInfo.HIT_WHISTLE, slider3.NodeSamples[0][1].Name);
Assert.AreEqual(HitSampleInfo.HIT_NORMAL, slider3.NodeSamples[0][0].Name);
Assert.AreEqual(HitSampleInfo.HIT_WHISTLE, slider3.NodeSamples[0][1].Name);
Assert.AreEqual(1, slider3.NodeSamples[1].Count);
Assert.AreEqual(SampleInfo.HIT_NORMAL, slider3.NodeSamples[1][0].Name);
Assert.AreEqual(HitSampleInfo.HIT_NORMAL, slider3.NodeSamples[1][0].Name);
Assert.AreEqual(2, slider3.NodeSamples[2].Count);
Assert.AreEqual(SampleInfo.HIT_NORMAL, slider3.NodeSamples[2][0].Name);
Assert.AreEqual(SampleInfo.HIT_CLAP, slider3.NodeSamples[2][1].Name);
Assert.AreEqual(HitSampleInfo.HIT_NORMAL, slider3.NodeSamples[2][0].Name);
Assert.AreEqual(HitSampleInfo.HIT_CLAP, slider3.NodeSamples[2][1].Name);
}
}
@ -475,12 +504,146 @@ namespace osu.Game.Tests.Beatmaps.Formats
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
using (var resStream = TestResources.OpenResource("hitobject-no-addition-bank.osu"))
using (var stream = new StreamReader(resStream))
using (var stream = new LineBufferedReader(resStream))
{
var hitObjects = decoder.Decode(stream).HitObjects;
Assert.AreEqual(hitObjects[0].Samples[0].Bank, hitObjects[0].Samples[1].Bank);
}
}
[Test]
public void TestInvalidEventStillPasses()
{
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
using (var badResStream = TestResources.OpenResource("invalid-events.osu"))
using (var badStream = new LineBufferedReader(badResStream))
{
Assert.DoesNotThrow(() => decoder.Decode(badStream));
}
}
[Test]
public void TestFallbackDecoderForCorruptedHeader()
{
Decoder<Beatmap> decoder = null;
Beatmap beatmap = null;
using (var resStream = TestResources.OpenResource("corrupted-header.osu"))
using (var stream = new LineBufferedReader(resStream))
{
Assert.DoesNotThrow(() => decoder = Decoder.GetDecoder<Beatmap>(stream));
Assert.IsInstanceOf<LegacyBeatmapDecoder>(decoder);
Assert.DoesNotThrow(() => beatmap = decoder.Decode(stream));
Assert.IsNotNull(beatmap);
Assert.AreEqual("Beatmap with corrupted header", beatmap.Metadata.Title);
Assert.AreEqual("Evil Hacker", beatmap.Metadata.AuthorString);
}
}
[Test]
public void TestFallbackDecoderForMissingHeader()
{
Decoder<Beatmap> decoder = null;
Beatmap beatmap = null;
using (var resStream = TestResources.OpenResource("missing-header.osu"))
using (var stream = new LineBufferedReader(resStream))
{
Assert.DoesNotThrow(() => decoder = Decoder.GetDecoder<Beatmap>(stream));
Assert.IsInstanceOf<LegacyBeatmapDecoder>(decoder);
Assert.DoesNotThrow(() => beatmap = decoder.Decode(stream));
Assert.IsNotNull(beatmap);
Assert.AreEqual("Beatmap with no header", beatmap.Metadata.Title);
Assert.AreEqual("Incredibly Evil Hacker", beatmap.Metadata.AuthorString);
}
}
[Test]
public void TestDecodeFileWithEmptyLinesAtStart()
{
Decoder<Beatmap> decoder = null;
Beatmap beatmap = null;
using (var resStream = TestResources.OpenResource("empty-lines-at-start.osu"))
using (var stream = new LineBufferedReader(resStream))
{
Assert.DoesNotThrow(() => decoder = Decoder.GetDecoder<Beatmap>(stream));
Assert.IsInstanceOf<LegacyBeatmapDecoder>(decoder);
Assert.DoesNotThrow(() => beatmap = decoder.Decode(stream));
Assert.IsNotNull(beatmap);
Assert.AreEqual("Empty lines at start", beatmap.Metadata.Title);
Assert.AreEqual("Edge Case Hunter", beatmap.Metadata.AuthorString);
}
}
[Test]
public void TestDecodeFileWithEmptyLinesAndNoHeader()
{
Decoder<Beatmap> decoder = null;
Beatmap beatmap = null;
using (var resStream = TestResources.OpenResource("empty-line-instead-of-header.osu"))
using (var stream = new LineBufferedReader(resStream))
{
Assert.DoesNotThrow(() => decoder = Decoder.GetDecoder<Beatmap>(stream));
Assert.IsInstanceOf<LegacyBeatmapDecoder>(decoder);
Assert.DoesNotThrow(() => beatmap = decoder.Decode(stream));
Assert.IsNotNull(beatmap);
Assert.AreEqual("The dog ate the file header", beatmap.Metadata.Title);
Assert.AreEqual("Why does this keep happening", beatmap.Metadata.AuthorString);
}
}
[Test]
public void TestDecodeFileWithContentImmediatelyAfterHeader()
{
Decoder<Beatmap> decoder = null;
Beatmap beatmap = null;
using (var resStream = TestResources.OpenResource("no-empty-line-after-header.osu"))
using (var stream = new LineBufferedReader(resStream))
{
Assert.DoesNotThrow(() => decoder = Decoder.GetDecoder<Beatmap>(stream));
Assert.IsInstanceOf<LegacyBeatmapDecoder>(decoder);
Assert.DoesNotThrow(() => beatmap = decoder.Decode(stream));
Assert.IsNotNull(beatmap);
Assert.AreEqual("No empty line delimiting header from contents", beatmap.Metadata.Title);
Assert.AreEqual("Edge Case Hunter", beatmap.Metadata.AuthorString);
}
}
[Test]
public void TestDecodeEmptyFile()
{
using (var resStream = new MemoryStream())
using (var stream = new LineBufferedReader(resStream))
{
Assert.Throws<IOException>(() => Decoder.GetDecoder<Beatmap>(stream));
}
}
[Test]
public void TestAllowFallbackDecoderOverwrite()
{
Decoder<Beatmap> decoder = null;
using (var resStream = TestResources.OpenResource("corrupted-header.osu"))
using (var stream = new LineBufferedReader(resStream))
{
Assert.DoesNotThrow(() => decoder = Decoder.GetDecoder<Beatmap>(stream));
Assert.IsInstanceOf<LegacyBeatmapDecoder>(decoder);
}
Assert.DoesNotThrow(LegacyDifficultyCalculatorBeatmapDecoder.Register);
using (var resStream = TestResources.OpenResource("corrupted-header.osu"))
using (var stream = new LineBufferedReader(resStream))
{
Assert.DoesNotThrow(() => decoder = Decoder.GetDecoder<Beatmap>(stream));
Assert.IsInstanceOf<LegacyDifficultyCalculatorBeatmapDecoder>(decoder);
}
}
}
}

View File

@ -2,9 +2,9 @@
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.IO;
using NUnit.Framework;
using osu.Game.Beatmaps.Formats;
using osu.Game.IO;
using osu.Game.Tests.Resources;
namespace osu.Game.Tests.Beatmaps.Formats
@ -18,7 +18,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
var decoder = new LineLoggingDecoder(14);
using (var resStream = TestResources.OpenResource("comments.osu"))
using (var stream = new StreamReader(resStream))
using (var stream = new LineBufferedReader(resStream))
{
decoder.Decode(stream);

View File

@ -1,12 +1,12 @@
// 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.IO;
using System.Linq;
using NUnit.Framework;
using osuTK;
using osu.Framework.Graphics;
using osu.Game.Beatmaps.Formats;
using osu.Game.IO;
using osu.Game.Storyboards;
using osu.Game.Tests.Resources;
@ -21,7 +21,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
var decoder = new LegacyStoryboardDecoder();
using (var resStream = TestResources.OpenResource("Himeringo - Yotsuya-san ni Yoroshiku (RLC) [Winber1's Extreme].osu"))
using (var stream = new StreamReader(resStream))
using (var stream = new LineBufferedReader(resStream))
{
var storyboard = decoder.Decode(stream);
@ -58,7 +58,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
int spriteCount = background.Elements.Count(x => x.GetType() == typeof(StoryboardSprite));
int animationCount = background.Elements.Count(x => x.GetType() == typeof(StoryboardAnimation));
int sampleCount = background.Elements.Count(x => x.GetType() == typeof(StoryboardSample));
int sampleCount = background.Elements.Count(x => x.GetType() == typeof(StoryboardSampleInfo));
Assert.AreEqual(15, spriteCount);
Assert.AreEqual(1, animationCount);
@ -94,7 +94,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
var decoder = new LegacyStoryboardDecoder();
using (var resStream = TestResources.OpenResource("variable-with-suffix.osb"))
using (var stream = new StreamReader(resStream))
using (var stream = new LineBufferedReader(resStream))
{
var storyboard = decoder.Decode(stream);

View File

@ -8,9 +8,10 @@ using NUnit.Framework;
using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Formats;
using osu.Game.IO;
using osu.Game.IO.Serialization;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Scoring;
using osu.Game.Tests.Resources;
using osuTK;
@ -101,14 +102,14 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.IsNotNull(curveData);
Assert.AreEqual(new Vector2(192, 168), positionData.Position);
Assert.AreEqual(956, beatmap.HitObjects[0].StartTime);
Assert.IsTrue(beatmap.HitObjects[0].Samples.Any(s => s.Name == SampleInfo.HIT_NORMAL));
Assert.IsTrue(beatmap.HitObjects[0].Samples.Any(s => s.Name == HitSampleInfo.HIT_NORMAL));
positionData = beatmap.HitObjects[1] as IHasPosition;
Assert.IsNotNull(positionData);
Assert.AreEqual(new Vector2(304, 56), positionData.Position);
Assert.AreEqual(1285, beatmap.HitObjects[1].StartTime);
Assert.IsTrue(beatmap.HitObjects[1].Samples.Any(s => s.Name == SampleInfo.HIT_CLAP));
Assert.IsTrue(beatmap.HitObjects[1].Samples.Any(s => s.Name == HitSampleInfo.HIT_CLAP));
}
[TestCase(normal)]
@ -148,13 +149,13 @@ namespace osu.Game.Tests.Beatmaps.Formats
private Beatmap decode(string filename, out Beatmap jsonDecoded)
{
using (var stream = TestResources.OpenResource(filename))
using (var sr = new StreamReader(stream))
using (var sr = new LineBufferedReader(stream))
{
var legacyDecoded = new LegacyBeatmapDecoder { ApplyOffsets = false }.Decode(sr);
using (var ms = new MemoryStream())
using (var sw = new StreamWriter(ms))
using (var sr2 = new StreamReader(ms))
using (var sr2 = new LineBufferedReader(ms))
{
sw.Write(legacyDecoded.Serialize());
sw.Flush();

View File

@ -11,9 +11,14 @@ using NUnit.Framework;
using osu.Framework.Platform;
using osu.Game.IPC;
using osu.Framework.Allocation;
using osu.Framework.Logging;
using osu.Game.Beatmaps;
using osu.Game.IO;
using osu.Game.Tests.Resources;
using SharpCompress.Archives;
using SharpCompress.Archives.Zip;
using SharpCompress.Common;
using SharpCompress.Writers.Zip;
namespace osu.Game.Tests.Beatmaps.IO
{
@ -21,14 +26,14 @@ namespace osu.Game.Tests.Beatmaps.IO
public class ImportBeatmapTest
{
[Test]
public void TestImportWhenClosed()
public async Task TestImportWhenClosed()
{
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportWhenClosed"))
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportWhenClosed)))
{
try
{
LoadOszIntoOsu(loadOsu(host));
await LoadOszIntoOsu(loadOsu(host));
}
finally
{
@ -38,16 +43,16 @@ namespace osu.Game.Tests.Beatmaps.IO
}
[Test]
public void TestImportThenDelete()
public async Task TestImportThenDelete()
{
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportThenDelete"))
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenDelete)))
{
try
{
var osu = loadOsu(host);
var imported = LoadOszIntoOsu(osu);
var imported = await LoadOszIntoOsu(osu);
deleteBeatmapSet(imported, osu);
}
@ -59,26 +64,24 @@ namespace osu.Game.Tests.Beatmaps.IO
}
[Test]
public void TestImportThenImport()
public async Task TestImportThenImport()
{
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportThenImport"))
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenImport)))
{
try
{
var osu = loadOsu(host);
var imported = LoadOszIntoOsu(osu);
var importedSecondTime = LoadOszIntoOsu(osu);
var imported = await LoadOszIntoOsu(osu);
var importedSecondTime = await LoadOszIntoOsu(osu);
// check the newly "imported" beatmap is actually just the restored previous import. since it matches hash.
Assert.IsTrue(imported.ID == importedSecondTime.ID);
Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Beatmaps.First().ID);
var manager = osu.Dependencies.Get<BeatmapManager>();
Assert.AreEqual(1, manager.GetAllUsableBeatmapSets().Count);
Assert.AreEqual(1, manager.QueryBeatmapSets(_ => true).ToList().Count);
checkBeatmapSetCount(osu, 1);
checkSingleReferencedFileCount(osu, 18);
}
finally
{
@ -88,34 +91,87 @@ namespace osu.Game.Tests.Beatmaps.IO
}
[Test]
public void TestRollbackOnFailure()
public async Task TestImportCorruptThenImport()
{
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestRollbackOnFailure"))
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportCorruptThenImport)))
{
try
{
var osu = loadOsu(host);
var imported = await LoadOszIntoOsu(osu);
var firstFile = imported.Files.First();
var files = osu.Dependencies.Get<FileStore>();
long originalLength;
using (var stream = files.Storage.GetStream(firstFile.FileInfo.StoragePath))
originalLength = stream.Length;
using (var stream = files.Storage.GetStream(firstFile.FileInfo.StoragePath, FileAccess.Write, FileMode.Create))
stream.WriteByte(0);
var importedSecondTime = await LoadOszIntoOsu(osu);
using (var stream = files.Storage.GetStream(firstFile.FileInfo.StoragePath))
Assert.AreEqual(stream.Length, originalLength, "Corruption was not fixed on second import");
// check the newly "imported" beatmap is actually just the restored previous import. since it matches hash.
Assert.IsTrue(imported.ID == importedSecondTime.ID);
Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Beatmaps.First().ID);
checkBeatmapSetCount(osu, 1);
checkSingleReferencedFileCount(osu, 18);
}
finally
{
host.Exit();
}
}
}
[Test]
public async Task TestRollbackOnFailure()
{
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestRollbackOnFailure)))
{
try
{
int itemAddRemoveFireCount = 0;
int loggedExceptionCount = 0;
Logger.NewEntry += l =>
{
if (l.Target == LoggingTarget.Database && l.Exception != null)
Interlocked.Increment(ref loggedExceptionCount);
};
var osu = loadOsu(host);
var manager = osu.Dependencies.Get<BeatmapManager>();
int fireCount = 0;
// ReSharper disable once AccessToModifiedClosure
manager.ItemAdded += (_, __) => fireCount++;
manager.ItemRemoved += _ => fireCount++;
manager.ItemAdded += _ => Interlocked.Increment(ref itemAddRemoveFireCount);
manager.ItemRemoved += _ => Interlocked.Increment(ref itemAddRemoveFireCount);
var imported = LoadOszIntoOsu(osu);
var imported = await LoadOszIntoOsu(osu);
Assert.AreEqual(0, fireCount -= 1);
Assert.AreEqual(0, itemAddRemoveFireCount -= 1);
imported.Hash += "-changed";
manager.Update(imported);
Assert.AreEqual(0, fireCount -= 2);
Assert.AreEqual(0, itemAddRemoveFireCount -= 2);
checkBeatmapSetCount(osu, 1);
checkBeatmapCount(osu, 12);
checkSingleReferencedFileCount(osu, 18);
var breakTemp = TestResources.GetTestBeatmapForImport();
MemoryStream brokenOsu = new MemoryStream(new byte[] { 1, 3, 3, 7 });
MemoryStream brokenOsu = new MemoryStream();
MemoryStream brokenOsz = new MemoryStream(File.ReadAllBytes(breakTemp));
File.Delete(breakTemp);
@ -124,22 +180,27 @@ namespace osu.Game.Tests.Beatmaps.IO
using (var zip = ZipArchive.Open(brokenOsz))
{
zip.AddEntry("broken.osu", brokenOsu, false);
zip.SaveTo(outStream, SharpCompress.Common.CompressionType.Deflate);
zip.SaveTo(outStream, CompressionType.Deflate);
}
Assert.AreEqual(1, manager.GetAllUsableBeatmapSets().Count);
Assert.AreEqual(1, manager.QueryBeatmapSets(_ => true).ToList().Count);
Assert.AreEqual(12, manager.QueryBeatmaps(_ => true).ToList().Count);
// this will trigger purging of the existing beatmap (online set id match) but should rollback due to broken osu.
manager.Import(breakTemp);
try
{
await manager.Import(breakTemp);
}
catch
{
}
// no events should be fired in the case of a rollback.
Assert.AreEqual(0, fireCount);
Assert.AreEqual(0, itemAddRemoveFireCount);
Assert.AreEqual(1, manager.GetAllUsableBeatmapSets().Count);
Assert.AreEqual(1, manager.QueryBeatmapSets(_ => true).ToList().Count);
Assert.AreEqual(12, manager.QueryBeatmaps(_ => true).ToList().Count);
checkBeatmapSetCount(osu, 1);
checkBeatmapCount(osu, 12);
checkSingleReferencedFileCount(osu, 18);
Assert.AreEqual(1, loggedExceptionCount);
}
finally
{
@ -149,29 +210,28 @@ namespace osu.Game.Tests.Beatmaps.IO
}
[Test]
public void TestImportThenImportDifferentHash()
public async Task TestImportThenImportDifferentHash()
{
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportThenImportDifferentHash"))
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenImportDifferentHash)))
{
try
{
var osu = loadOsu(host);
var manager = osu.Dependencies.Get<BeatmapManager>();
var imported = LoadOszIntoOsu(osu);
var imported = await LoadOszIntoOsu(osu);
imported.Hash += "-changed";
manager.Update(imported);
var importedSecondTime = LoadOszIntoOsu(osu);
var importedSecondTime = await LoadOszIntoOsu(osu);
Assert.IsTrue(imported.ID != importedSecondTime.ID);
Assert.IsTrue(imported.Beatmaps.First().ID < importedSecondTime.Beatmaps.First().ID);
// only one beatmap will exist as the online set ID matched, causing purging of the first import.
Assert.AreEqual(1, manager.GetAllUsableBeatmapSets().Count);
Assert.AreEqual(1, manager.QueryBeatmapSets(_ => true).ToList().Count);
checkBeatmapSetCount(osu, 1);
}
finally
{
@ -181,20 +241,20 @@ namespace osu.Game.Tests.Beatmaps.IO
}
[Test]
public void TestImportThenDeleteThenImport()
public async Task TestImportThenDeleteThenImport()
{
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportThenDeleteThenImport"))
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenDeleteThenImport)))
{
try
{
var osu = loadOsu(host);
var imported = LoadOszIntoOsu(osu);
var imported = await LoadOszIntoOsu(osu);
deleteBeatmapSet(imported, osu);
var importedSecondTime = LoadOszIntoOsu(osu);
var importedSecondTime = await LoadOszIntoOsu(osu);
// check the newly "imported" beatmap is actually just the restored previous import. since it matches hash.
Assert.IsTrue(imported.ID == importedSecondTime.ID);
@ -209,16 +269,16 @@ namespace osu.Game.Tests.Beatmaps.IO
[TestCase(true)]
[TestCase(false)]
public void TestImportThenDeleteThenImportWithOnlineIDMismatch(bool set)
public async Task TestImportThenDeleteThenImportWithOnlineIDMismatch(bool set)
{
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
using (HeadlessGameHost host = new CleanRunHeadlessGameHost($"TestImportThenDeleteThenImport-{set}"))
using (HeadlessGameHost host = new CleanRunHeadlessGameHost($"{nameof(TestImportThenDeleteThenImportWithOnlineIDMismatch)}-{set}"))
{
try
{
var osu = loadOsu(host);
var imported = LoadOszIntoOsu(osu);
var imported = await LoadOszIntoOsu(osu);
if (set)
imported.OnlineBeatmapSetID = 1234;
@ -229,7 +289,7 @@ namespace osu.Game.Tests.Beatmaps.IO
deleteBeatmapSet(imported, osu);
var importedSecondTime = LoadOszIntoOsu(osu);
var importedSecondTime = await LoadOszIntoOsu(osu);
// check the newly "imported" beatmap has been reimported due to mismatch (even though hashes matched)
Assert.IsTrue(imported.ID != importedSecondTime.ID);
@ -243,10 +303,10 @@ namespace osu.Game.Tests.Beatmaps.IO
}
[Test]
public void TestImportWithDuplicateBeatmapIDs()
public async Task TestImportWithDuplicateBeatmapIDs()
{
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportWithDuplicateBeatmapID"))
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportWithDuplicateBeatmapIDs)))
{
try
{
@ -284,7 +344,7 @@ namespace osu.Game.Tests.Beatmaps.IO
var manager = osu.Dependencies.Get<BeatmapManager>();
var imported = manager.Import(toImport);
var imported = await manager.Import(toImport);
Assert.NotNull(imported);
Assert.AreEqual(null, imported.Beatmaps[0].OnlineBeatmapID);
@ -330,16 +390,16 @@ namespace osu.Game.Tests.Beatmaps.IO
}
[Test]
public void TestImportWhenFileOpen()
public async Task TestImportWhenFileOpen()
{
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportWhenFileOpen"))
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportWhenFileOpen)))
{
try
{
var osu = loadOsu(host);
var temp = TestResources.GetTestBeatmapForImport();
using (File.OpenRead(temp))
osu.Dependencies.Get<BeatmapManager>().Import(temp);
await osu.Dependencies.Get<BeatmapManager>().Import(temp);
ensureLoaded(osu);
File.Delete(temp);
Assert.IsFalse(File.Exists(temp), "We likely held a read lock on the file when we shouldn't");
@ -351,13 +411,154 @@ namespace osu.Game.Tests.Beatmaps.IO
}
}
public static BeatmapSetInfo LoadOszIntoOsu(OsuGameBase osu, string path = null)
[Test]
public async Task TestImportWithDuplicateHashes()
{
var temp = path ?? TestResources.GetTestBeatmapForImport();
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportNestedStructure)))
{
try
{
var osu = loadOsu(host);
var temp = TestResources.GetTestBeatmapForImport();
string extractedFolder = $"{temp}_extracted";
Directory.CreateDirectory(extractedFolder);
try
{
using (var zip = ZipArchive.Open(temp))
zip.WriteToDirectory(extractedFolder);
using (var zip = ZipArchive.Create())
{
zip.AddAllFromDirectory(extractedFolder);
zip.AddEntry("duplicate.osu", Directory.GetFiles(extractedFolder, "*.osu").First());
zip.SaveTo(temp, new ZipWriterOptions(CompressionType.Deflate));
}
await osu.Dependencies.Get<BeatmapManager>().Import(temp);
ensureLoaded(osu);
}
finally
{
Directory.Delete(extractedFolder, true);
}
}
finally
{
host.Exit();
}
}
}
[Test]
public async Task TestImportNestedStructure()
{
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportNestedStructure)))
{
try
{
var osu = loadOsu(host);
var temp = TestResources.GetTestBeatmapForImport();
string extractedFolder = $"{temp}_extracted";
string subfolder = Path.Combine(extractedFolder, "subfolder");
Directory.CreateDirectory(subfolder);
try
{
using (var zip = ZipArchive.Open(temp))
zip.WriteToDirectory(subfolder);
using (var zip = ZipArchive.Create())
{
zip.AddAllFromDirectory(extractedFolder);
zip.SaveTo(temp, new ZipWriterOptions(CompressionType.Deflate));
}
var imported = await osu.Dependencies.Get<BeatmapManager>().Import(temp);
ensureLoaded(osu);
Assert.IsFalse(imported.Files.Any(f => f.Filename.Contains("subfolder")), "Files contain common subfolder");
}
finally
{
Directory.Delete(extractedFolder, true);
}
}
finally
{
host.Exit();
}
}
}
[Test]
public async Task TestImportWithIgnoredDirectoryInArchive()
{
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportWithIgnoredDirectoryInArchive)))
{
try
{
var osu = loadOsu(host);
var temp = TestResources.GetTestBeatmapForImport();
string extractedFolder = $"{temp}_extracted";
string dataFolder = Path.Combine(extractedFolder, "actual_data");
string resourceForkFolder = Path.Combine(extractedFolder, "__MACOSX");
string resourceForkFilePath = Path.Combine(resourceForkFolder, ".extracted");
Directory.CreateDirectory(dataFolder);
Directory.CreateDirectory(resourceForkFolder);
using (var resourceForkFile = File.CreateText(resourceForkFilePath))
{
resourceForkFile.WriteLine("adding content so that it's not empty");
}
try
{
using (var zip = ZipArchive.Open(temp))
zip.WriteToDirectory(dataFolder);
using (var zip = ZipArchive.Create())
{
zip.AddAllFromDirectory(extractedFolder);
zip.SaveTo(temp, new ZipWriterOptions(CompressionType.Deflate));
}
var imported = await osu.Dependencies.Get<BeatmapManager>().Import(temp);
ensureLoaded(osu);
Assert.IsFalse(imported.Files.Any(f => f.Filename.Contains("__MACOSX")), "Files contain resource fork folder, which should be ignored");
Assert.IsFalse(imported.Files.Any(f => f.Filename.Contains("actual_data")), "Files contain common subfolder");
}
finally
{
Directory.Delete(extractedFolder, true);
}
}
finally
{
host.Exit();
}
}
}
public static async Task<BeatmapSetInfo> LoadOszIntoOsu(OsuGameBase osu, string path = null, bool virtualTrack = false)
{
var temp = path ?? TestResources.GetTestBeatmapForImport(virtualTrack);
var manager = osu.Dependencies.Get<BeatmapManager>();
manager.Import(temp);
await manager.Import(temp);
var imported = manager.GetAllUsableBeatmapSets();
@ -373,11 +574,32 @@ namespace osu.Game.Tests.Beatmaps.IO
var manager = osu.Dependencies.Get<BeatmapManager>();
manager.Delete(imported);
Assert.IsTrue(manager.GetAllUsableBeatmapSets().Count == 0);
Assert.AreEqual(1, manager.QueryBeatmapSets(_ => true).ToList().Count);
checkBeatmapSetCount(osu, 0);
checkBeatmapSetCount(osu, 1, true);
checkSingleReferencedFileCount(osu, 0);
Assert.IsTrue(manager.QueryBeatmapSets(_ => true).First().DeletePending);
}
private void checkBeatmapSetCount(OsuGameBase osu, int expected, bool includeDeletePending = false)
{
var manager = osu.Dependencies.Get<BeatmapManager>();
Assert.AreEqual(expected, includeDeletePending
? manager.QueryBeatmapSets(_ => true).ToList().Count
: manager.GetAllUsableBeatmapSets().Count);
}
private void checkBeatmapCount(OsuGameBase osu, int expected)
{
Assert.AreEqual(expected, osu.Dependencies.Get<BeatmapManager>().QueryBeatmaps(_ => true).ToList().Count);
}
private void checkSingleReferencedFileCount(OsuGameBase osu, int expected)
{
Assert.AreEqual(expected, osu.Dependencies.Get<FileStore>().QueryFiles(f => f.ReferenceCount == 1).Count());
}
private OsuGameBase loadOsu(GameHost host)
{
var osu = new OsuGameBase();

View File

@ -0,0 +1,125 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.IO;
using System.Text;
using NUnit.Framework;
using osu.Game.IO;
namespace osu.Game.Tests.Beatmaps.IO
{
[TestFixture]
public class LineBufferedReaderTest
{
[Test]
public void TestReadLineByLine()
{
const string contents = "line 1\rline 2\nline 3";
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(contents)))
using (var bufferedReader = new LineBufferedReader(stream))
{
Assert.AreEqual("line 1", bufferedReader.ReadLine());
Assert.AreEqual("line 2", bufferedReader.ReadLine());
Assert.AreEqual("line 3", bufferedReader.ReadLine());
Assert.IsNull(bufferedReader.ReadLine());
}
}
[Test]
public void TestPeekLineOnce()
{
const string contents = "line 1\r\npeek this\nline 3";
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(contents)))
using (var bufferedReader = new LineBufferedReader(stream))
{
Assert.AreEqual("line 1", bufferedReader.ReadLine());
Assert.AreEqual("peek this", bufferedReader.PeekLine());
Assert.AreEqual("peek this", bufferedReader.ReadLine());
Assert.AreEqual("line 3", bufferedReader.ReadLine());
Assert.IsNull(bufferedReader.ReadLine());
}
}
[Test]
public void TestPeekLineMultipleTimes()
{
const string contents = "peek this once\nline 2\rpeek this a lot";
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(contents)))
using (var bufferedReader = new LineBufferedReader(stream))
{
Assert.AreEqual("peek this once", bufferedReader.PeekLine());
Assert.AreEqual("peek this once", bufferedReader.ReadLine());
Assert.AreEqual("line 2", bufferedReader.ReadLine());
Assert.AreEqual("peek this a lot", bufferedReader.PeekLine());
Assert.AreEqual("peek this a lot", bufferedReader.PeekLine());
Assert.AreEqual("peek this a lot", bufferedReader.PeekLine());
Assert.AreEqual("peek this a lot", bufferedReader.ReadLine());
Assert.IsNull(bufferedReader.ReadLine());
}
}
[Test]
public void TestPeekLineAtEndOfStream()
{
const string contents = "first line\r\nsecond line";
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(contents)))
using (var bufferedReader = new LineBufferedReader(stream))
{
Assert.AreEqual("first line", bufferedReader.ReadLine());
Assert.AreEqual("second line", bufferedReader.ReadLine());
Assert.IsNull(bufferedReader.PeekLine());
Assert.IsNull(bufferedReader.ReadLine());
Assert.IsNull(bufferedReader.PeekLine());
}
}
[Test]
public void TestPeekReadLineOnEmptyStream()
{
using (var stream = new MemoryStream())
using (var bufferedReader = new LineBufferedReader(stream))
{
Assert.IsNull(bufferedReader.PeekLine());
Assert.IsNull(bufferedReader.ReadLine());
Assert.IsNull(bufferedReader.ReadLine());
Assert.IsNull(bufferedReader.PeekLine());
}
}
[Test]
public void TestReadToEndNoPeeks()
{
const string contents = "first line\r\nsecond line";
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(contents)))
using (var bufferedReader = new LineBufferedReader(stream))
{
Assert.AreEqual(contents, bufferedReader.ReadToEnd());
}
}
[Test]
public void TestReadToEndAfterReadsAndPeeks()
{
const string contents = "this line is gone\rthis one shouldn't be\r\nthese ones\ndefinitely not";
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(contents)))
using (var bufferedReader = new LineBufferedReader(stream))
{
Assert.AreEqual("this line is gone", bufferedReader.ReadLine());
Assert.AreEqual("this one shouldn't be", bufferedReader.PeekLine());
var endingLines = bufferedReader.ReadToEnd().Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
Assert.AreEqual(3, endingLines.Length);
Assert.AreEqual("this one shouldn't be", endingLines[0]);
Assert.AreEqual("these ones", endingLines[1]);
Assert.AreEqual("definitely not", endingLines[2]);
}
}
}
}

View File

@ -7,6 +7,7 @@ using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Tests.Resources;
using osu.Game.Beatmaps.Formats;
using osu.Game.IO;
using osu.Game.IO.Archives;
namespace osu.Game.Tests.Beatmaps.IO
@ -50,7 +51,7 @@ namespace osu.Game.Tests.Beatmaps.IO
Beatmap beatmap;
using (var stream = new StreamReader(reader.GetStream("Soleily - Renatus (Deif) [Platter].osu")))
using (var stream = new LineBufferedReader(reader.GetStream("Soleily - Renatus (Deif) [Platter].osu")))
beatmap = Decoder.GetDecoder<Beatmap>(stream).Decode(stream);
var meta = beatmap.Metadata;

View File

@ -0,0 +1,116 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
using NUnit.Framework;
using osu.Game.Rulesets.Objects;
namespace osu.Game.Tests.Beatmaps
{
[TestFixture]
public class SliderEventGenerationTest
{
private const double start_time = 0;
private const double span_duration = 1000;
[Test]
public void TestSingleSpan()
{
var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 1, null).ToArray();
Assert.That(events[0].Type, Is.EqualTo(SliderEventType.Head));
Assert.That(events[0].Time, Is.EqualTo(start_time));
Assert.That(events[1].Type, Is.EqualTo(SliderEventType.Tick));
Assert.That(events[1].Time, Is.EqualTo(span_duration / 2));
Assert.That(events[3].Type, Is.EqualTo(SliderEventType.Tail));
Assert.That(events[3].Time, Is.EqualTo(span_duration));
}
[Test]
public void TestRepeat()
{
var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 2, null).ToArray();
Assert.That(events[0].Type, Is.EqualTo(SliderEventType.Head));
Assert.That(events[0].Time, Is.EqualTo(start_time));
Assert.That(events[1].Type, Is.EqualTo(SliderEventType.Tick));
Assert.That(events[1].Time, Is.EqualTo(span_duration / 2));
Assert.That(events[2].Type, Is.EqualTo(SliderEventType.Repeat));
Assert.That(events[2].Time, Is.EqualTo(span_duration));
Assert.That(events[3].Type, Is.EqualTo(SliderEventType.Tick));
Assert.That(events[3].Time, Is.EqualTo(span_duration + span_duration / 2));
Assert.That(events[5].Type, Is.EqualTo(SliderEventType.Tail));
Assert.That(events[5].Time, Is.EqualTo(2 * span_duration));
}
[Test]
public void TestNonEvenTicks()
{
var events = SliderEventGenerator.Generate(start_time, span_duration, 1, 300, span_duration, 2, null).ToArray();
Assert.That(events[0].Type, Is.EqualTo(SliderEventType.Head));
Assert.That(events[0].Time, Is.EqualTo(start_time));
Assert.That(events[1].Type, Is.EqualTo(SliderEventType.Tick));
Assert.That(events[1].Time, Is.EqualTo(300));
Assert.That(events[2].Type, Is.EqualTo(SliderEventType.Tick));
Assert.That(events[2].Time, Is.EqualTo(600));
Assert.That(events[3].Type, Is.EqualTo(SliderEventType.Tick));
Assert.That(events[3].Time, Is.EqualTo(900));
Assert.That(events[4].Type, Is.EqualTo(SliderEventType.Repeat));
Assert.That(events[4].Time, Is.EqualTo(span_duration));
Assert.That(events[5].Type, Is.EqualTo(SliderEventType.Tick));
Assert.That(events[5].Time, Is.EqualTo(1100));
Assert.That(events[6].Type, Is.EqualTo(SliderEventType.Tick));
Assert.That(events[6].Time, Is.EqualTo(1400));
Assert.That(events[7].Type, Is.EqualTo(SliderEventType.Tick));
Assert.That(events[7].Time, Is.EqualTo(1700));
Assert.That(events[9].Type, Is.EqualTo(SliderEventType.Tail));
Assert.That(events[9].Time, Is.EqualTo(2 * span_duration));
}
[Test]
public void TestLegacyLastTickOffset()
{
var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 1, 100).ToArray();
Assert.That(events[2].Type, Is.EqualTo(SliderEventType.LegacyLastTick));
Assert.That(events[2].Time, Is.EqualTo(900));
}
[Test]
public void TestMinimumTickDistance()
{
const double velocity = 5;
const double min_distance = velocity * 10;
var events = SliderEventGenerator.Generate(start_time, span_duration, velocity, velocity, span_duration, 2, 0).ToArray();
Assert.Multiple(() =>
{
int tickIndex = -1;
while (++tickIndex < events.Length)
{
if (events[tickIndex].Type != SliderEventType.Tick)
continue;
Assert.That(events[tickIndex].Time, Is.LessThan(span_duration - min_distance).Or.GreaterThan(span_duration + min_distance));
}
});
}
}
}

View File

@ -119,6 +119,53 @@ namespace osu.Game.Tests.Chat
Assert.AreEqual(11, result.Links[0].Length);
}
[Test]
public void TestOldFormatLinkWithBalancedBrackets()
{
Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a (tricky (one))[https://osu.ppy.sh]!" });
Assert.AreEqual("This is a tricky (one)!", result.DisplayContent);
Assert.AreEqual(1, result.Links.Count);
Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
Assert.AreEqual(10, result.Links[0].Index);
Assert.AreEqual(12, result.Links[0].Length);
}
[Test]
public void TestOldFormatLinkWithEscapedBrackets()
{
Message result = MessageFormatter.FormatMessage(new Message { Content = "This is (another loose bracket \\))[https://osu.ppy.sh]." });
Assert.AreEqual("This is another loose bracket ).", result.DisplayContent);
Assert.AreEqual(1, result.Links.Count);
Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
Assert.AreEqual(8, result.Links[0].Index);
Assert.AreEqual(23, result.Links[0].Length);
}
[Test]
public void TestOldFormatWithBackslashes()
{
Message result = MessageFormatter.FormatMessage(new Message { Content = "This link (should end with a backslash \\)[https://osu.ppy.sh]." });
Assert.AreEqual("This link should end with a backslash \\.", result.DisplayContent);
Assert.AreEqual(1, result.Links.Count);
Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
Assert.AreEqual(10, result.Links[0].Index);
Assert.AreEqual(29, result.Links[0].Length);
}
[Test]
public void TestOldFormatLinkWithEscapedAndBalancedBrackets()
{
Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a (\\)super\\(\\( tricky (one))[https://osu.ppy.sh]!" });
Assert.AreEqual("This is a )super(( tricky (one)!", result.DisplayContent);
Assert.AreEqual(1, result.Links.Count);
Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
Assert.AreEqual(10, result.Links[0].Index);
Assert.AreEqual(21, result.Links[0].Length);
}
[Test]
public void TestNewFormatLink()
{
@ -131,6 +178,42 @@ namespace osu.Game.Tests.Chat
Assert.AreEqual(11, result.Links[0].Length);
}
[Test]
public void TestNewFormatLinkWithEscapedBrackets()
{
Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a [https://osu.ppy.sh nasty link with escaped brackets: \\] and \\[]" });
Assert.AreEqual("This is a nasty link with escaped brackets: ] and [", result.DisplayContent);
Assert.AreEqual(1, result.Links.Count);
Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
Assert.AreEqual(10, result.Links[0].Index);
Assert.AreEqual(41, result.Links[0].Length);
}
[Test]
public void TestNewFormatLinkWithBackslashesInside()
{
Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a [https://osu.ppy.sh link \\ with \\ backslashes \\]" });
Assert.AreEqual("This is a link \\ with \\ backslashes \\", result.DisplayContent);
Assert.AreEqual(1, result.Links.Count);
Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
Assert.AreEqual(10, result.Links[0].Index);
Assert.AreEqual(27, result.Links[0].Length);
}
[Test]
public void TestNewFormatLinkWithEscapedAndBalancedBrackets()
{
Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a [https://osu.ppy.sh [link [with \\] too many brackets \\[ ]]]" });
Assert.AreEqual("This is a [link [with ] too many brackets [ ]]", result.DisplayContent);
Assert.AreEqual(1, result.Links.Count);
Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
Assert.AreEqual(10, result.Links[0].Index);
Assert.AreEqual(36, result.Links[0].Length);
}
[Test]
public void TestMarkdownFormatLink()
{
@ -143,6 +226,143 @@ namespace osu.Game.Tests.Chat
Assert.AreEqual(11, result.Links[0].Length);
}
[Test]
public void TestMarkdownFormatLinkWithBalancedBrackets()
{
Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a [tricky [one]](https://osu.ppy.sh)!" });
Assert.AreEqual("This is a tricky [one]!", result.DisplayContent);
Assert.AreEqual(1, result.Links.Count);
Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
Assert.AreEqual(10, result.Links[0].Index);
Assert.AreEqual(12, result.Links[0].Length);
}
[Test]
public void TestMarkdownFormatLinkWithEscapedBrackets()
{
Message result = MessageFormatter.FormatMessage(new Message { Content = "This is [another loose bracket \\]](https://osu.ppy.sh)." });
Assert.AreEqual("This is another loose bracket ].", result.DisplayContent);
Assert.AreEqual(1, result.Links.Count);
Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
Assert.AreEqual(8, result.Links[0].Index);
Assert.AreEqual(23, result.Links[0].Length);
}
[Test]
public void TestMarkdownFormatWithBackslashes()
{
Message result = MessageFormatter.FormatMessage(new Message { Content = "This link [should end with a backslash \\](https://osu.ppy.sh)." });
Assert.AreEqual("This link should end with a backslash \\.", result.DisplayContent);
Assert.AreEqual(1, result.Links.Count);
Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
Assert.AreEqual(10, result.Links[0].Index);
Assert.AreEqual(29, result.Links[0].Length);
}
[Test]
public void TestMarkdownFormatLinkWithEscapedAndBalancedBrackets()
{
Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a [\\]super\\[\\[ tricky [one]](https://osu.ppy.sh)!" });
Assert.AreEqual("This is a ]super[[ tricky [one]!", result.DisplayContent);
Assert.AreEqual(1, result.Links.Count);
Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
Assert.AreEqual(10, result.Links[0].Index);
Assert.AreEqual(21, result.Links[0].Length);
}
[Test]
public void TestMarkdownFormatLinkWithInlineTitle()
{
Message result = MessageFormatter.FormatMessage(new Message { Content = "I haven't seen [this link format](https://osu.ppy.sh \"osu!\") before..." });
Assert.AreEqual("I haven't seen this link format before...", result.DisplayContent);
Assert.AreEqual(1, result.Links.Count);
Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
Assert.AreEqual(15, result.Links[0].Index);
Assert.AreEqual(16, result.Links[0].Length);
}
[Test]
public void TestMarkdownFormatLinkWithInlineTitleAndEscapedQuotes()
{
Message result = MessageFormatter.FormatMessage(new Message { Content = "I haven't seen [this link format](https://osu.ppy.sh \"inner quote \\\" just to confuse \") before..." });
Assert.AreEqual("I haven't seen this link format before...", result.DisplayContent);
Assert.AreEqual(1, result.Links.Count);
Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
Assert.AreEqual(15, result.Links[0].Index);
Assert.AreEqual(16, result.Links[0].Length);
}
[Test]
public void TestMarkdownFormatLinkWithUrlInTextAndInlineTitle()
{
Message result = MessageFormatter.FormatMessage(new Message { Content = "I haven't seen [https://osu.ppy.sh](https://osu.ppy.sh \"https://osu.ppy.sh\") before..." });
Assert.AreEqual("I haven't seen https://osu.ppy.sh before...", result.DisplayContent);
Assert.AreEqual(1, result.Links.Count);
Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
Assert.AreEqual(15, result.Links[0].Index);
Assert.AreEqual(18, result.Links[0].Length);
}
[Test]
public void TestMarkdownFormatLinkWithUrlAndTextInTitle()
{
Message result = MessageFormatter.FormatMessage(new Message { Content = "I haven't seen [oh no, text here! https://osu.ppy.sh](https://osu.ppy.sh) before..." });
Assert.AreEqual("I haven't seen oh no, text here! https://osu.ppy.sh before...", result.DisplayContent);
Assert.AreEqual(1, result.Links.Count);
Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
Assert.AreEqual(15, result.Links[0].Index);
Assert.AreEqual(36, result.Links[0].Length);
}
[Test]
public void TestMarkdownFormatLinkWithMisleadingUrlInText()
{
Message result = MessageFormatter.FormatMessage(new Message { Content = "I haven't seen [https://google.com](https://osu.ppy.sh) before..." });
Assert.AreEqual("I haven't seen https://google.com before...", result.DisplayContent);
Assert.AreEqual(1, result.Links.Count);
Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
Assert.AreEqual(15, result.Links[0].Index);
Assert.AreEqual(18, result.Links[0].Length);
}
[Test]
public void TestMarkdownFormatLinkThatContractsIntoLargerLink()
{
Message result = MessageFormatter.FormatMessage(new Message { Content = "super broken https://[osu.ppy](https://reddit.com).sh/" });
Assert.AreEqual("super broken https://osu.ppy.sh/", result.DisplayContent);
Assert.AreEqual(1, result.Links.Count);
Assert.AreEqual("https://reddit.com", result.Links[0].Url);
Assert.AreEqual(21, result.Links[0].Index);
Assert.AreEqual(7, result.Links[0].Length);
}
[Test]
public void TestMarkdownFormatLinkDirectlyNextToRawLink()
{
// the raw link has a port at the end of it, so that the raw link regex terminates at the port and doesn't consume display text from the formatted one
Message result = MessageFormatter.FormatMessage(new Message { Content = "https://localhost:8080[https://osu.ppy.sh](https://osu.ppy.sh) should be two links" });
Assert.AreEqual("https://localhost:8080https://osu.ppy.sh should be two links", result.DisplayContent);
Assert.AreEqual(2, result.Links.Count);
Assert.AreEqual("https://localhost:8080", result.Links[0].Url);
Assert.AreEqual(0, result.Links[0].Index);
Assert.AreEqual(22, result.Links[0].Length);
Assert.AreEqual("https://osu.ppy.sh", result.Links[1].Url);
Assert.AreEqual(22, result.Links[1].Index);
Assert.AreEqual(18, result.Links[1].Length);
}
[Test]
public void TestChannelLink()
{

View File

@ -0,0 +1,194 @@
// 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.Testing;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Edit;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit;
using osu.Game.Tests.Visual;
namespace osu.Game.Tests.Editor
{
[HeadlessTest]
public class TestSceneHitObjectComposerDistanceSnapping : EditorClockTestScene
{
private TestHitObjectComposer composer;
[SetUp]
public void Setup() => Schedule(() =>
{
Child = composer = new TestHitObjectComposer();
BeatDivisor.Value = 1;
composer.EditorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 1;
composer.EditorBeatmap.ControlPointInfo.Clear();
composer.EditorBeatmap.ControlPointInfo.Add(0, new DifficultyControlPoint { SpeedMultiplier = 1 });
composer.EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 1000 });
});
[TestCase(1)]
[TestCase(2)]
public void TestSliderMultiplier(float multiplier)
{
AddStep($"set multiplier = {multiplier}", () => composer.EditorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = multiplier);
assertSnapDistance(100 * multiplier);
}
[TestCase(1)]
[TestCase(2)]
public void TestSpeedMultiplier(float multiplier)
{
AddStep($"set multiplier = {multiplier}", () =>
{
composer.EditorBeatmap.ControlPointInfo.Clear();
composer.EditorBeatmap.ControlPointInfo.Add(0, new DifficultyControlPoint { SpeedMultiplier = multiplier });
});
assertSnapDistance(100 * multiplier);
}
[TestCase(1)]
[TestCase(2)]
public void TestBeatDivisor(int divisor)
{
AddStep($"set divisor = {divisor}", () => BeatDivisor.Value = divisor);
assertSnapDistance(100f / divisor);
}
[Test]
public void TestConvertDurationToDistance()
{
assertDurationToDistance(500, 50);
assertDurationToDistance(1000, 100);
AddStep("set slider multiplier = 2", () => composer.EditorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 2);
assertDurationToDistance(500, 100);
assertDurationToDistance(1000, 200);
AddStep("set beat length = 500", () =>
{
composer.EditorBeatmap.ControlPointInfo.Clear();
composer.EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 500 });
});
assertDurationToDistance(500, 200);
assertDurationToDistance(1000, 400);
}
[Test]
public void TestConvertDistanceToDuration()
{
assertDistanceToDuration(50, 500);
assertDistanceToDuration(100, 1000);
AddStep("set slider multiplier = 2", () => composer.EditorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 2);
assertDistanceToDuration(100, 500);
assertDistanceToDuration(200, 1000);
AddStep("set beat length = 500", () =>
{
composer.EditorBeatmap.ControlPointInfo.Clear();
composer.EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 500 });
});
assertDistanceToDuration(200, 500);
assertDistanceToDuration(400, 1000);
}
[Test]
public void TestGetSnappedDurationFromDistance()
{
assertSnappedDuration(50, 0);
assertSnappedDuration(100, 1000);
assertSnappedDuration(150, 1000);
assertSnappedDuration(200, 2000);
assertSnappedDuration(250, 2000);
AddStep("set slider multiplier = 2", () => composer.EditorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 2);
assertSnappedDuration(50, 0);
assertSnappedDuration(100, 0);
assertSnappedDuration(150, 0);
assertSnappedDuration(200, 1000);
assertSnappedDuration(250, 1000);
AddStep("set beat length = 500", () =>
{
composer.EditorBeatmap.ControlPointInfo.Clear();
composer.EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 500 });
});
assertSnappedDuration(50, 0);
assertSnappedDuration(100, 0);
assertSnappedDuration(150, 0);
assertSnappedDuration(200, 500);
assertSnappedDuration(250, 500);
assertSnappedDuration(400, 1000);
}
[Test]
public void GetSnappedDistanceFromDistance()
{
assertSnappedDistance(50, 0);
assertSnappedDistance(100, 100);
assertSnappedDistance(150, 100);
assertSnappedDistance(200, 200);
assertSnappedDistance(250, 200);
AddStep("set slider multiplier = 2", () => composer.EditorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 2);
assertSnappedDistance(50, 0);
assertSnappedDistance(100, 0);
assertSnappedDistance(150, 0);
assertSnappedDistance(200, 200);
assertSnappedDistance(250, 200);
AddStep("set beat length = 500", () =>
{
composer.EditorBeatmap.ControlPointInfo.Clear();
composer.EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 500 });
});
assertSnappedDistance(50, 0);
assertSnappedDistance(100, 0);
assertSnappedDistance(150, 0);
assertSnappedDistance(200, 200);
assertSnappedDistance(250, 200);
assertSnappedDistance(400, 400);
}
private void assertSnapDistance(float expectedDistance)
=> AddAssert($"distance is {expectedDistance}", () => composer.GetBeatSnapDistanceAt(0) == expectedDistance);
private void assertDurationToDistance(double duration, float expectedDistance)
=> AddAssert($"duration = {duration} -> distance = {expectedDistance}", () => composer.DurationToDistance(0, duration) == expectedDistance);
private void assertDistanceToDuration(float distance, double expectedDuration)
=> AddAssert($"distance = {distance} -> duration = {expectedDuration}", () => composer.DistanceToDuration(0, distance) == expectedDuration);
private void assertSnappedDuration(float distance, double expectedDuration)
=> AddAssert($"distance = {distance} -> duration = {expectedDuration} (snapped)", () => composer.GetSnappedDurationFromDistance(0, distance) == expectedDuration);
private void assertSnappedDistance(float distance, float expectedDistance)
=> AddAssert($"distance = {distance} -> distance = {expectedDistance} (snapped)", () => composer.GetSnappedDistanceFromDistance(0, distance) == expectedDistance);
private class TestHitObjectComposer : OsuHitObjectComposer
{
public new EditorBeatmap<OsuHitObject> EditorBeatmap => base.EditorBeatmap;
public TestHitObjectComposer()
: base(new OsuRuleset())
{
}
}
}
}

View File

@ -0,0 +1,143 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Textures;
using osu.Framework.Testing;
using osu.Game.Audio;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Skinning;
using osu.Game.Tests.Visual;
using osuTK.Graphics;
namespace osu.Game.Tests.Gameplay
{
[HeadlessTest]
public class TestSceneHitObjectAccentColour : OsuTestScene
{
private Container skinContainer;
[SetUp]
public void Setup() => Schedule(() => Child = skinContainer = new SkinProvidingContainer(new TestSkin()));
[Test]
public void TestChangeComboIndexBeforeLoad()
{
TestDrawableHitObject hitObject = null;
AddStep("set combo and add hitobject", () =>
{
hitObject = new TestDrawableHitObject();
hitObject.HitObject.ComboIndex = 1;
skinContainer.Add(hitObject);
});
AddAssert("combo colour is green", () => hitObject.AccentColour.Value == Color4.Green);
}
[Test]
public void TestChangeComboIndexDuringLoad()
{
TestDrawableHitObject hitObject = null;
AddStep("add hitobject and set combo", () =>
{
skinContainer.Add(hitObject = new TestDrawableHitObject());
hitObject.HitObject.ComboIndex = 1;
});
AddAssert("combo colour is green", () => hitObject.AccentColour.Value == Color4.Green);
}
[Test]
public void TestChangeComboIndexAfterLoad()
{
TestDrawableHitObject hitObject = null;
AddStep("add hitobject", () => skinContainer.Add(hitObject = new TestDrawableHitObject()));
AddAssert("combo colour is red", () => hitObject.AccentColour.Value == Color4.Red);
AddStep("change combo", () => hitObject.HitObject.ComboIndex = 1);
AddAssert("combo colour is green", () => hitObject.AccentColour.Value == Color4.Green);
}
private class TestDrawableHitObject : DrawableHitObject<TestHitObjectWithCombo>
{
public TestDrawableHitObject()
: base(new TestHitObjectWithCombo())
{
}
}
private class TestHitObjectWithCombo : HitObject, IHasComboInformation
{
public bool NewCombo { get; } = false;
public int ComboOffset { get; } = 0;
public Bindable<int> IndexInCurrentComboBindable { get; } = new Bindable<int>();
public int IndexInCurrentCombo
{
get => IndexInCurrentComboBindable.Value;
set => IndexInCurrentComboBindable.Value = value;
}
public Bindable<int> ComboIndexBindable { get; } = new Bindable<int>();
public int ComboIndex
{
get => ComboIndexBindable.Value;
set => ComboIndexBindable.Value = value;
}
public Bindable<bool> LastInComboBindable { get; } = new Bindable<bool>();
public bool LastInCombo
{
get => LastInComboBindable.Value;
set => LastInComboBindable.Value = value;
}
}
private class TestSkin : ISkin
{
public readonly List<Color4> ComboColours = new List<Color4>
{
Color4.Red,
Color4.Green
};
public Drawable GetDrawableComponent(ISkinComponent component) => throw new NotImplementedException();
public Texture GetTexture(string componentName) => throw new NotImplementedException();
public SampleChannel GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup)
{
switch (lookup)
{
case GlobalSkinConfiguration global:
switch (global)
{
case GlobalSkinConfiguration.ComboColours:
return SkinUtils.As<TValue>(new Bindable<List<Color4>>(ComboColours));
}
break;
}
throw new NotImplementedException();
}
}
}
}

View File

@ -0,0 +1,48 @@
// 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.Beatmaps;
namespace osu.Game.Tests.NonVisual
{
[TestFixture]
public class BeatmapSetInfoEqualityTest
{
[Test]
public void TestOnlineWithOnline()
{
var ourInfo = new BeatmapSetInfo { OnlineBeatmapSetID = 123 };
var otherInfo = new BeatmapSetInfo { OnlineBeatmapSetID = 123 };
Assert.AreEqual(ourInfo, otherInfo);
}
[Test]
public void TestDatabasedWithDatabased()
{
var ourInfo = new BeatmapSetInfo { ID = 123 };
var otherInfo = new BeatmapSetInfo { ID = 123 };
Assert.AreEqual(ourInfo, otherInfo);
}
[Test]
public void TestDatabasedWithOnline()
{
var ourInfo = new BeatmapSetInfo { ID = 123, OnlineBeatmapSetID = 12 };
var otherInfo = new BeatmapSetInfo { OnlineBeatmapSetID = 12 };
Assert.AreEqual(ourInfo, otherInfo);
}
[Test]
public void TestCheckNullID()
{
var ourInfo = new BeatmapSetInfo { Status = BeatmapSetOnlineStatus.Loved };
var otherInfo = new BeatmapSetInfo { Status = BeatmapSetOnlineStatus.Approved };
Assert.AreNotEqual(ourInfo, otherInfo);
}
}
}

View File

@ -0,0 +1,227 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
using NUnit.Framework;
using osu.Game.Beatmaps.ControlPoints;
namespace osu.Game.Tests.NonVisual
{
[TestFixture]
public class ControlPointInfoTest
{
[Test]
public void TestAdd()
{
var cpi = new ControlPointInfo();
cpi.Add(0, new TimingControlPoint());
cpi.Add(1000, new TimingControlPoint { BeatLength = 500 });
Assert.That(cpi.Groups.Count, Is.EqualTo(2));
Assert.That(cpi.TimingPoints.Count, Is.EqualTo(2));
Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(2));
}
[Test]
public void TestAddRedundantTiming()
{
var cpi = new ControlPointInfo();
cpi.Add(0, new TimingControlPoint()); // is *not* redundant, special exception for first timing point.
cpi.Add(1000, new TimingControlPoint()); // is redundant
Assert.That(cpi.Groups.Count, Is.EqualTo(1));
Assert.That(cpi.TimingPoints.Count, Is.EqualTo(1));
Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(1));
}
[Test]
public void TestAddRedundantDifficulty()
{
var cpi = new ControlPointInfo();
cpi.Add(0, new DifficultyControlPoint()); // is redundant
cpi.Add(1000, new DifficultyControlPoint()); // is redundant
Assert.That(cpi.Groups.Count, Is.EqualTo(0));
Assert.That(cpi.TimingPoints.Count, Is.EqualTo(0));
Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(0));
cpi.Add(1000, new DifficultyControlPoint { SpeedMultiplier = 2 }); // is not redundant
Assert.That(cpi.Groups.Count, Is.EqualTo(1));
Assert.That(cpi.DifficultyPoints.Count, Is.EqualTo(1));
Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(1));
}
[Test]
public void TestAddRedundantSample()
{
var cpi = new ControlPointInfo();
cpi.Add(0, new SampleControlPoint()); // is redundant
cpi.Add(1000, new SampleControlPoint()); // is redundant
Assert.That(cpi.Groups.Count, Is.EqualTo(0));
Assert.That(cpi.TimingPoints.Count, Is.EqualTo(0));
Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(0));
cpi.Add(1000, new SampleControlPoint { SampleVolume = 50 }); // is not redundant
Assert.That(cpi.Groups.Count, Is.EqualTo(1));
Assert.That(cpi.SamplePoints.Count, Is.EqualTo(1));
Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(1));
}
[Test]
public void TestAddRedundantEffect()
{
var cpi = new ControlPointInfo();
cpi.Add(0, new EffectControlPoint()); // is redundant
cpi.Add(1000, new EffectControlPoint()); // is redundant
Assert.That(cpi.Groups.Count, Is.EqualTo(0));
Assert.That(cpi.TimingPoints.Count, Is.EqualTo(0));
Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(0));
cpi.Add(1000, new EffectControlPoint { KiaiMode = true }); // is not redundant
Assert.That(cpi.Groups.Count, Is.EqualTo(1));
Assert.That(cpi.EffectPoints.Count, Is.EqualTo(1));
Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(1));
}
[Test]
public void TestAddGroup()
{
var cpi = new ControlPointInfo();
var group = cpi.GroupAt(1000, true);
var group2 = cpi.GroupAt(1000, true);
Assert.That(group, Is.EqualTo(group2));
Assert.That(cpi.Groups.Count, Is.EqualTo(1));
}
[Test]
public void TestGroupAtLookupOnly()
{
var cpi = new ControlPointInfo();
var group = cpi.GroupAt(5000, true);
Assert.That(group, Is.Not.Null);
Assert.That(cpi.Groups.Count, Is.EqualTo(1));
Assert.That(cpi.GroupAt(1000), Is.Null);
Assert.That(cpi.GroupAt(5000), Is.Not.Null);
}
[Test]
public void TestAddRemoveGroup()
{
var cpi = new ControlPointInfo();
var group = cpi.GroupAt(1000, true);
Assert.That(cpi.Groups.Count, Is.EqualTo(1));
cpi.RemoveGroup(group);
Assert.That(cpi.Groups.Count, Is.EqualTo(0));
}
[Test]
public void TestAddControlPointToGroup()
{
var cpi = new ControlPointInfo();
var group = cpi.GroupAt(1000, true);
Assert.That(cpi.Groups.Count, Is.EqualTo(1));
// usually redundant, but adding to group forces it to be added
group.Add(new DifficultyControlPoint());
Assert.That(group.ControlPoints.Count, Is.EqualTo(1));
Assert.That(cpi.DifficultyPoints.Count, Is.EqualTo(1));
}
[Test]
public void TestAddDuplicateControlPointToGroup()
{
var cpi = new ControlPointInfo();
var group = cpi.GroupAt(1000, true);
Assert.That(cpi.Groups.Count, Is.EqualTo(1));
group.Add(new DifficultyControlPoint());
group.Add(new DifficultyControlPoint { SpeedMultiplier = 2 });
Assert.That(group.ControlPoints.Count, Is.EqualTo(1));
Assert.That(cpi.DifficultyPoints.Count, Is.EqualTo(1));
Assert.That(cpi.DifficultyPoints.First().SpeedMultiplier, Is.EqualTo(2));
}
[Test]
public void TestRemoveControlPointFromGroup()
{
var cpi = new ControlPointInfo();
var group = cpi.GroupAt(1000, true);
Assert.That(cpi.Groups.Count, Is.EqualTo(1));
var difficultyPoint = new DifficultyControlPoint();
group.Add(difficultyPoint);
group.Remove(difficultyPoint);
Assert.That(group.ControlPoints.Count, Is.EqualTo(0));
Assert.That(cpi.DifficultyPoints.Count, Is.EqualTo(0));
Assert.That(cpi.AllControlPoints.Count, Is.EqualTo(0));
}
[Test]
public void TestOrdering()
{
var cpi = new ControlPointInfo();
cpi.Add(0, new TimingControlPoint());
cpi.Add(1000, new TimingControlPoint { BeatLength = 500 });
cpi.Add(10000, new TimingControlPoint { BeatLength = 200 });
cpi.Add(5000, new TimingControlPoint { BeatLength = 100 });
cpi.Add(3000, new DifficultyControlPoint { SpeedMultiplier = 2 });
cpi.GroupAt(7000, true).Add(new DifficultyControlPoint { SpeedMultiplier = 4 });
cpi.GroupAt(1000).Add(new SampleControlPoint { SampleVolume = 0 });
cpi.GroupAt(8000, true).Add(new EffectControlPoint { KiaiMode = true });
Assert.That(cpi.AllControlPoints.Count, Is.EqualTo(8));
Assert.That(cpi.Groups, Is.Ordered.Ascending.By(nameof(ControlPointGroup.Time)));
Assert.That(cpi.AllControlPoints, Is.Ordered.Ascending.By(nameof(ControlPoint.Time)));
Assert.That(cpi.TimingPoints, Is.Ordered.Ascending.By(nameof(ControlPoint.Time)));
}
[Test]
public void TestClear()
{
var cpi = new ControlPointInfo();
cpi.Add(0, new TimingControlPoint());
cpi.Add(1000, new TimingControlPoint { BeatLength = 500 });
cpi.Add(10000, new TimingControlPoint { BeatLength = 200 });
cpi.Add(5000, new TimingControlPoint { BeatLength = 100 });
cpi.Add(3000, new DifficultyControlPoint { SpeedMultiplier = 2 });
cpi.GroupAt(7000, true).Add(new DifficultyControlPoint { SpeedMultiplier = 4 });
cpi.GroupAt(1000).Add(new SampleControlPoint { SampleVolume = 0 });
cpi.GroupAt(8000, true).Add(new EffectControlPoint { KiaiMode = true });
cpi.Clear();
Assert.That(cpi.Groups.Count, Is.EqualTo(0));
Assert.That(cpi.DifficultyPoints.Count, Is.EqualTo(0));
Assert.That(cpi.AllControlPoints.Count, Is.EqualTo(0));
}
}
}

View File

@ -0,0 +1,201 @@
// 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.Beatmaps;
using osu.Game.Rulesets;
using osu.Game.Screens.Select;
using osu.Game.Screens.Select.Carousel;
namespace osu.Game.Tests.NonVisual.Filtering
{
[TestFixture]
public class FilterMatchingTest
{
private BeatmapInfo getExampleBeatmap() => new BeatmapInfo
{
Ruleset = new RulesetInfo { ID = 5 },
StarDifficulty = 4.0d,
BaseDifficulty = new BeatmapDifficulty
{
ApproachRate = 5.0f,
DrainRate = 3.0f,
CircleSize = 2.0f,
},
Metadata = new BeatmapMetadata
{
Artist = "The Artist",
ArtistUnicode = "check unicode too",
Title = "Title goes here",
TitleUnicode = "Title goes here",
AuthorString = "The Author",
Source = "unit tests",
Tags = "look for tags too",
},
Version = "version as well",
Length = 2500,
BPM = 160,
BeatDivisor = 12,
Status = BeatmapSetOnlineStatus.Loved
};
[Test]
public void TestCriteriaMatchingNoRuleset()
{
var exampleBeatmapInfo = getExampleBeatmap();
var criteria = new FilterCriteria();
var carouselItem = new CarouselBeatmap(exampleBeatmapInfo);
carouselItem.Filter(criteria);
Assert.IsFalse(carouselItem.Filtered.Value);
}
[Test]
public void TestCriteriaMatchingSpecificRuleset()
{
var exampleBeatmapInfo = getExampleBeatmap();
var criteria = new FilterCriteria
{
Ruleset = new RulesetInfo { ID = 6 }
};
var carouselItem = new CarouselBeatmap(exampleBeatmapInfo);
carouselItem.Filter(criteria);
Assert.IsTrue(carouselItem.Filtered.Value);
}
[Test]
public void TestCriteriaMatchingConvertedBeatmaps()
{
var exampleBeatmapInfo = getExampleBeatmap();
var criteria = new FilterCriteria
{
Ruleset = new RulesetInfo { ID = 6 },
AllowConvertedBeatmaps = true
};
var carouselItem = new CarouselBeatmap(exampleBeatmapInfo);
carouselItem.Filter(criteria);
Assert.IsFalse(carouselItem.Filtered.Value);
}
[Test]
[TestCase(true)]
[TestCase(false)]
public void TestCriteriaMatchingRangeMin(bool inclusive)
{
var exampleBeatmapInfo = getExampleBeatmap();
var criteria = new FilterCriteria
{
Ruleset = new RulesetInfo { ID = 6 },
AllowConvertedBeatmaps = true,
ApproachRate = new FilterCriteria.OptionalRange<float>
{
IsLowerInclusive = inclusive,
Min = 5.0f
}
};
var carouselItem = new CarouselBeatmap(exampleBeatmapInfo);
carouselItem.Filter(criteria);
Assert.AreEqual(!inclusive, carouselItem.Filtered.Value);
}
[Test]
[TestCase(true)]
[TestCase(false)]
public void TestCriteriaMatchingRangeMax(bool inclusive)
{
var exampleBeatmapInfo = getExampleBeatmap();
var criteria = new FilterCriteria
{
Ruleset = new RulesetInfo { ID = 6 },
AllowConvertedBeatmaps = true,
BPM = new FilterCriteria.OptionalRange<double>
{
IsUpperInclusive = inclusive,
Max = 160d
}
};
var carouselItem = new CarouselBeatmap(exampleBeatmapInfo);
carouselItem.Filter(criteria);
Assert.AreEqual(!inclusive, carouselItem.Filtered.Value);
}
[Test]
[TestCase("artist", false)]
[TestCase("artist title author", false)]
[TestCase("an artist", true)]
[TestCase("tags too", false)]
[TestCase("version", false)]
[TestCase("an auteur", true)]
public void TestCriteriaMatchingTerms(string terms, bool filtered)
{
var exampleBeatmapInfo = getExampleBeatmap();
var criteria = new FilterCriteria
{
Ruleset = new RulesetInfo { ID = 6 },
AllowConvertedBeatmaps = true,
SearchText = terms
};
var carouselItem = new CarouselBeatmap(exampleBeatmapInfo);
carouselItem.Filter(criteria);
Assert.AreEqual(filtered, carouselItem.Filtered.Value);
}
[Test]
[TestCase("", false)]
[TestCase("The", false)]
[TestCase("THE", false)]
[TestCase("author", false)]
[TestCase("the author", false)]
[TestCase("the author AND then something else", true)]
[TestCase("unknown", true)]
public void TestCriteriaMatchingCreator(string creatorName, bool filtered)
{
var exampleBeatmapInfo = getExampleBeatmap();
var criteria = new FilterCriteria
{
Creator = new FilterCriteria.OptionalTextFilter { SearchTerm = creatorName }
};
var carouselItem = new CarouselBeatmap(exampleBeatmapInfo);
carouselItem.Filter(criteria);
Assert.AreEqual(filtered, carouselItem.Filtered.Value);
}
[Test]
[TestCase("", false)]
[TestCase("The", false)]
[TestCase("THE", false)]
[TestCase("artist", false)]
[TestCase("the artist", false)]
[TestCase("the artist AND then something else", true)]
[TestCase("unicode too", false)]
[TestCase("unknown", true)]
public void TestCriteriaMatchingArtist(string artistName, bool filtered)
{
var exampleBeatmapInfo = getExampleBeatmap();
var criteria = new FilterCriteria
{
Artist = new FilterCriteria.OptionalTextFilter { SearchTerm = artistName }
};
var carouselItem = new CarouselBeatmap(exampleBeatmapInfo);
carouselItem.Filter(criteria);
Assert.AreEqual(filtered, carouselItem.Filtered.Value);
}
[Test]
[TestCase("", false)]
[TestCase("artist", false)]
[TestCase("unknown", true)]
public void TestCriteriaMatchingArtistWithNullUnicodeName(string artistName, bool filtered)
{
var exampleBeatmapInfo = getExampleBeatmap();
exampleBeatmapInfo.Metadata.ArtistUnicode = null;
var criteria = new FilterCriteria
{
Artist = new FilterCriteria.OptionalTextFilter { SearchTerm = artistName }
};
var carouselItem = new CarouselBeatmap(exampleBeatmapInfo);
carouselItem.Filter(criteria);
Assert.AreEqual(filtered, carouselItem.Filtered.Value);
}
}
}

View File

@ -0,0 +1,184 @@
// 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.Game.Beatmaps;
using osu.Game.Screens.Select;
namespace osu.Game.Tests.NonVisual.Filtering
{
[TestFixture]
public class FilterQueryParserTest
{
[Test]
public void TestApplyQueriesBareWords()
{
const string query = "looking for a beatmap";
var filterCriteria = new FilterCriteria();
FilterQueryParser.ApplyQueries(filterCriteria, query);
Assert.AreEqual("looking for a beatmap", filterCriteria.SearchText);
Assert.AreEqual(4, filterCriteria.SearchTerms.Length);
}
/*
* The following tests have been written a bit strangely (they don't check exact
* bound equality with what the filter says).
* This is to account for floating-point arithmetic issues.
* For example, specifying a bpm<140 filter would previously match beatmaps with BPM
* of 139.99999, which would be displayed in the UI as 140.
* Due to this the tests check the last tick inside the range and the first tick
* outside of the range.
*/
[Test]
public void TestApplyStarQueries()
{
const string query = "stars<4 easy";
var filterCriteria = new FilterCriteria();
FilterQueryParser.ApplyQueries(filterCriteria, query);
Assert.AreEqual("easy", filterCriteria.SearchText.Trim());
Assert.AreEqual(1, filterCriteria.SearchTerms.Length);
Assert.IsNotNull(filterCriteria.StarDifficulty.Max);
Assert.Greater(filterCriteria.StarDifficulty.Max, 3.99d);
Assert.Less(filterCriteria.StarDifficulty.Max, 4.00d);
Assert.IsNull(filterCriteria.StarDifficulty.Min);
}
[Test]
public void TestApplyApproachRateQueries()
{
const string query = "ar>=9 difficult";
var filterCriteria = new FilterCriteria();
FilterQueryParser.ApplyQueries(filterCriteria, query);
Assert.AreEqual("difficult", filterCriteria.SearchText.Trim());
Assert.AreEqual(1, filterCriteria.SearchTerms.Length);
Assert.IsNotNull(filterCriteria.ApproachRate.Min);
Assert.Greater(filterCriteria.ApproachRate.Min, 8.9f);
Assert.Less(filterCriteria.ApproachRate.Min, 9.0f);
Assert.IsNull(filterCriteria.ApproachRate.Max);
}
[Test]
public void TestApplyDrainRateQueries()
{
const string query = "dr>2 quite specific dr<:6";
var filterCriteria = new FilterCriteria();
FilterQueryParser.ApplyQueries(filterCriteria, query);
Assert.AreEqual("quite specific", filterCriteria.SearchText.Trim());
Assert.AreEqual(2, filterCriteria.SearchTerms.Length);
Assert.Greater(filterCriteria.DrainRate.Min, 2.0f);
Assert.Less(filterCriteria.DrainRate.Min, 2.1f);
Assert.Greater(filterCriteria.DrainRate.Max, 6.0f);
Assert.Less(filterCriteria.DrainRate.Min, 6.1f);
}
[Test]
public void TestApplyBPMQueries()
{
const string query = "bpm>:200 gotta go fast";
var filterCriteria = new FilterCriteria();
FilterQueryParser.ApplyQueries(filterCriteria, query);
Assert.AreEqual("gotta go fast", filterCriteria.SearchText.Trim());
Assert.AreEqual(3, filterCriteria.SearchTerms.Length);
Assert.IsNotNull(filterCriteria.BPM.Min);
Assert.Greater(filterCriteria.BPM.Min, 199.99d);
Assert.Less(filterCriteria.BPM.Min, 200.00d);
Assert.IsNull(filterCriteria.BPM.Max);
}
private static object[] lengthQueryExamples =
{
new object[] { "6ms", TimeSpan.FromMilliseconds(6), TimeSpan.FromMilliseconds(1) },
new object[] { "23s", TimeSpan.FromSeconds(23), TimeSpan.FromSeconds(1) },
new object[] { "9m", TimeSpan.FromMinutes(9), TimeSpan.FromMinutes(1) },
new object[] { "0.25h", TimeSpan.FromHours(0.25), TimeSpan.FromHours(1) },
new object[] { "70", TimeSpan.FromSeconds(70), TimeSpan.FromSeconds(1) },
};
[Test]
[TestCaseSource(nameof(lengthQueryExamples))]
public void TestApplyLengthQueries(string lengthQuery, TimeSpan expectedLength, TimeSpan scale)
{
string query = $"length={lengthQuery} time";
var filterCriteria = new FilterCriteria();
FilterQueryParser.ApplyQueries(filterCriteria, query);
Assert.AreEqual("time", filterCriteria.SearchText.Trim());
Assert.AreEqual(1, filterCriteria.SearchTerms.Length);
Assert.AreEqual(expectedLength.TotalMilliseconds - scale.TotalMilliseconds / 2.0, filterCriteria.Length.Min);
Assert.AreEqual(expectedLength.TotalMilliseconds + scale.TotalMilliseconds / 2.0, filterCriteria.Length.Max);
}
[Test]
public void TestApplyDivisorQueries()
{
const string query = "that's a time signature alright! divisor:12";
var filterCriteria = new FilterCriteria();
FilterQueryParser.ApplyQueries(filterCriteria, query);
Assert.AreEqual("that's a time signature alright!", filterCriteria.SearchText.Trim());
Assert.AreEqual(5, filterCriteria.SearchTerms.Length);
Assert.AreEqual(12, filterCriteria.BeatDivisor.Min);
Assert.IsTrue(filterCriteria.BeatDivisor.IsLowerInclusive);
Assert.AreEqual(12, filterCriteria.BeatDivisor.Max);
Assert.IsTrue(filterCriteria.BeatDivisor.IsUpperInclusive);
}
[Test]
public void TestApplyStatusQueries()
{
const string query = "I want the pp status=ranked";
var filterCriteria = new FilterCriteria();
FilterQueryParser.ApplyQueries(filterCriteria, query);
Assert.AreEqual("I want the pp", filterCriteria.SearchText.Trim());
Assert.AreEqual(4, filterCriteria.SearchTerms.Length);
Assert.AreEqual(BeatmapSetOnlineStatus.Ranked, filterCriteria.OnlineStatus.Min);
Assert.IsTrue(filterCriteria.OnlineStatus.IsLowerInclusive);
Assert.AreEqual(BeatmapSetOnlineStatus.Ranked, filterCriteria.OnlineStatus.Max);
Assert.IsTrue(filterCriteria.OnlineStatus.IsUpperInclusive);
}
[Test]
public void TestApplyCreatorQueries()
{
const string query = "beatmap specifically by creator=my_fav";
var filterCriteria = new FilterCriteria();
FilterQueryParser.ApplyQueries(filterCriteria, query);
Assert.AreEqual("beatmap specifically by", filterCriteria.SearchText.Trim());
Assert.AreEqual(3, filterCriteria.SearchTerms.Length);
Assert.AreEqual("my_fav", filterCriteria.Creator.SearchTerm);
}
[Test]
public void TestApplyArtistQueries()
{
const string query = "find me songs by artist=singer please";
var filterCriteria = new FilterCriteria();
FilterQueryParser.ApplyQueries(filterCriteria, query);
Assert.AreEqual("find me songs by please", filterCriteria.SearchText.Trim());
Assert.AreEqual(5, filterCriteria.SearchTerms.Length);
Assert.AreEqual("singer", filterCriteria.Artist.SearchTerm);
}
[Test]
public void TestApplyArtistQueriesWithSpaces()
{
const string query = "really like artist=\"name with space\" yes";
var filterCriteria = new FilterCriteria();
FilterQueryParser.ApplyQueries(filterCriteria, query);
Assert.AreEqual("really like yes", filterCriteria.SearchText.Trim());
Assert.AreEqual(3, filterCriteria.SearchTerms.Length);
Assert.AreEqual("name with space", filterCriteria.Artist.SearchTerm);
}
[Test]
public void TestApplyArtistQueriesOneDoubleQuote()
{
const string query = "weird artist=double\"quote";
var filterCriteria = new FilterCriteria();
FilterQueryParser.ApplyQueries(filterCriteria, query);
Assert.AreEqual("weird", filterCriteria.SearchText.Trim());
Assert.AreEqual(1, filterCriteria.SearchTerms.Length);
Assert.AreEqual("double\"quote", filterCriteria.Artist.SearchTerm);
}
}
}

View File

@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using NUnit.Framework;
using osu.Game.Replays;
@ -9,7 +10,7 @@ using osu.Game.Rulesets.Replays;
namespace osu.Game.Tests.NonVisual
{
[TestFixture]
public class FramedReplayinputHandlerTest
public class FramedReplayInputHandlerTest
{
private Replay replay;
private TestInputHandler handler;
@ -160,10 +161,7 @@ namespace osu.Game.Tests.NonVisual
[Test]
public void TestRewindInsideImportantSection()
{
// fast forward to important section
while (handler.SetFrameFromTime(3000) != null)
{
}
fastForwardToPoint(3000);
setTime(4000, 4000);
confirmCurrentFrame(4);
@ -205,10 +203,7 @@ namespace osu.Game.Tests.NonVisual
[Test]
public void TestRewindOutOfImportantSection()
{
// fast forward to important section
while (handler.SetFrameFromTime(3500) != null)
{
}
fastForwardToPoint(3500);
confirmCurrentFrame(3);
confirmNextFrame(4);
@ -227,6 +222,17 @@ namespace osu.Game.Tests.NonVisual
confirmNextFrame(2);
}
private void fastForwardToPoint(double destination)
{
for (int i = 0; i < 1000; i++)
{
if (handler.SetFrameFromTime(destination) == null)
return;
}
throw new TimeoutException("Seek was never fulfilled");
}
private void setTime(double set, double? expect)
{
Assert.AreEqual(expect, handler.SetFrameFromTime(set));
@ -274,6 +280,7 @@ namespace osu.Game.Tests.NonVisual
public TestInputHandler(Replay replay)
: base(replay)
{
FrameAccuratePlayback = true;
}
protected override double AllowedImportantTimeSpan => 1000;

View File

@ -11,13 +11,13 @@ namespace osu.Game.Tests.Resources
{
public static Stream OpenResource(string name) => new DllResourceStore("osu.Game.Tests.dll").GetStream($"Resources/{name}");
public static Stream GetTestBeatmapStream() => new DllResourceStore("osu.Game.Resources.dll").GetStream("Beatmaps/241526 Soleily - Renatus.osz");
public static Stream GetTestBeatmapStream(bool virtualTrack = false) => new DllResourceStore("osu.Game.Resources.dll").GetStream($"Beatmaps/241526 Soleily - Renatus{(virtualTrack ? "_virtual" : "")}.osz");
public static string GetTestBeatmapForImport()
public static string GetTestBeatmapForImport(bool virtualTrack = false)
{
var temp = Path.GetTempFileName() + ".osz";
using (var stream = GetTestBeatmapStream())
using (var stream = GetTestBeatmapStream(virtualTrack))
using (var newFile = File.Create(temp))
stream.CopyTo(newFile);

View File

@ -0,0 +1,8 @@
osu file format v7
[TimingPoints]
0,100,4,2,0,100,1,0
12,500,4,2,0,100,1,0
1000,-10,4,2,0,100,0,0
2000,-54,4,2,0,100,0,0
3000,-200,4,2,0,100,0,0

View File

@ -0,0 +1,5 @@
ow computerosu file format v14
[Metadata]
Title: Beatmap with corrupted header
Creator: Evil Hacker

View File

@ -0,0 +1,5 @@

[Metadata]
Title: The dog ate the file header
Creator: Why does this keep happening

View File

@ -0,0 +1,8 @@

osu file format v14
[Metadata]
Title: Empty lines at start
Creator: Edge Case Hunter

View File

@ -0,0 +1,14 @@
osu file format v14
[Events]
bad,event,this,should,fail
//Background and Video events
0,0,"machinetop_background.jpg",0,0
//Break Periods
2,122474,140135
//Storyboard Layer 0 (Background)
this,is,also,bad
//Storyboard Layer 1 (Fail)
//Storyboard Layer 2 (Pass)
//Storyboard Layer 3 (Foreground)
//Storyboard Sound Samples

View File

@ -0,0 +1,4 @@
[Metadata]
Title: Beatmap with no header
Creator: Incredibly Evil Hacker

View File

@ -0,0 +1,4 @@
osu file format v14
[Metadata]
Title: No empty line delimiting header from contents
Creator: Edge Case Hunter

View File

@ -1,5 +1,6 @@
[General]
Name: test skin
TestLookup: TestValue
[Colours]
Combo1 : 142,199,255

View File

@ -0,0 +1,5 @@
osu file format v14
[TimingPoints]
0,-200,4,1,0,100,0,0
2000,100,1,1,0,100,1,0

View File

@ -23,13 +23,13 @@ namespace osu.Game.Tests.Scores.IO
public class ImportScoreTest
{
[Test]
public void TestBasicImport()
public async Task TestBasicImport()
{
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestBasicImport"))
{
try
{
var osu = loadOsu(host);
var osu = await loadOsu(host);
var toImport = new ScoreInfo
{
@ -43,7 +43,7 @@ namespace osu.Game.Tests.Scores.IO
OnlineScoreID = 12345,
};
var imported = loadIntoOsu(osu, toImport);
var imported = await loadIntoOsu(osu, toImport);
Assert.AreEqual(toImport.Rank, imported.Rank);
Assert.AreEqual(toImport.TotalScore, imported.TotalScore);
@ -62,20 +62,20 @@ namespace osu.Game.Tests.Scores.IO
}
[Test]
public void TestImportMods()
public async Task TestImportMods()
{
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportMods"))
{
try
{
var osu = loadOsu(host);
var osu = await loadOsu(host);
var toImport = new ScoreInfo
{
Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() },
};
var imported = loadIntoOsu(osu, toImport);
var imported = await loadIntoOsu(osu, toImport);
Assert.IsTrue(imported.Mods.Any(m => m is OsuModHardRock));
Assert.IsTrue(imported.Mods.Any(m => m is OsuModDoubleTime));
@ -88,13 +88,13 @@ namespace osu.Game.Tests.Scores.IO
}
[Test]
public void TestImportStatistics()
public async Task TestImportStatistics()
{
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportStatistics"))
{
try
{
var osu = loadOsu(host);
var osu = await loadOsu(host);
var toImport = new ScoreInfo
{
@ -105,7 +105,7 @@ namespace osu.Game.Tests.Scores.IO
}
};
var imported = loadIntoOsu(osu, toImport);
var imported = await loadIntoOsu(osu, toImport);
Assert.AreEqual(toImport.Statistics[HitResult.Perfect], imported.Statistics[HitResult.Perfect]);
Assert.AreEqual(toImport.Statistics[HitResult.Miss], imported.Statistics[HitResult.Miss]);
@ -117,28 +117,72 @@ namespace osu.Game.Tests.Scores.IO
}
}
private ScoreInfo loadIntoOsu(OsuGameBase osu, ScoreInfo score)
[Test]
public async Task TestImportWithDeletedBeatmapSet()
{
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportWithDeletedBeatmapSet"))
{
try
{
var osu = await loadOsu(host);
var toImport = new ScoreInfo
{
Hash = Guid.NewGuid().ToString(),
Statistics = new Dictionary<HitResult, int>
{
{ HitResult.Perfect, 100 },
{ HitResult.Miss, 50 }
}
};
var imported = await loadIntoOsu(osu, toImport);
var beatmapManager = osu.Dependencies.Get<BeatmapManager>();
var scoreManager = osu.Dependencies.Get<ScoreManager>();
beatmapManager.Delete(beatmapManager.QueryBeatmapSet(s => s.Beatmaps.Any(b => b.ID == imported.Beatmap.ID)));
Assert.That(scoreManager.Query(s => s.ID == imported.ID).DeletePending, Is.EqualTo(true));
var secondImport = await loadIntoOsu(osu, imported);
Assert.That(secondImport, Is.Null);
}
finally
{
host.Exit();
}
}
}
private async Task<ScoreInfo> loadIntoOsu(OsuGameBase osu, ScoreInfo score)
{
var beatmapManager = osu.Dependencies.Get<BeatmapManager>();
score.Beatmap = beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps.First();
score.Ruleset = new OsuRuleset().RulesetInfo;
if (score.Beatmap == null)
score.Beatmap = beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps.First();
if (score.Ruleset == null)
score.Ruleset = new OsuRuleset().RulesetInfo;
var scoreManager = osu.Dependencies.Get<ScoreManager>();
scoreManager.Import(score);
await scoreManager.Import(score);
return scoreManager.GetAllUsableScores().First();
return scoreManager.GetAllUsableScores().FirstOrDefault();
}
private OsuGameBase loadOsu(GameHost host)
private async Task<OsuGameBase> loadOsu(GameHost host)
{
var osu = new OsuGameBase();
#pragma warning disable 4014
Task.Run(() => host.Run(osu));
#pragma warning restore 4014
waitForOrAssert(() => osu.IsLoaded, @"osu! failed to start in a reasonable amount of time");
var beatmapFile = TestResources.GetTestBeatmapForImport();
var beatmapManager = osu.Dependencies.Get<BeatmapManager>();
beatmapManager.Import(beatmapFile);
await beatmapManager.Import(beatmapFile);
return osu;
}

View File

@ -2,8 +2,8 @@
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.IO;
using NUnit.Framework;
using osu.Game.IO;
using osu.Game.Skinning;
using osu.Game.Tests.Resources;
using osuTK.Graphics;
@ -20,12 +20,14 @@ namespace osu.Game.Tests.Skins
var decoder = new LegacySkinDecoder();
using (var resStream = TestResources.OpenResource(hasColours ? "skin.ini" : "skin-empty.ini"))
using (var stream = new StreamReader(resStream))
using (var stream = new LineBufferedReader(resStream))
{
var comboColors = decoder.Decode(stream).ComboColours;
List<Color4> expectedColors;
if (hasColours)
{
expectedColors = new List<Color4>
{
new Color4(142, 199, 255, 255),
@ -33,6 +35,7 @@ namespace osu.Game.Tests.Skins
new Color4(128, 255, 255, 255),
new Color4(100, 100, 100, 100),
};
}
else
expectedColors = new DefaultSkin().Configuration.ComboColours;
@ -41,5 +44,20 @@ namespace osu.Game.Tests.Skins
Assert.AreEqual(expectedColors[i], comboColors[i]);
}
}
[Test]
public void TestDecodeGeneral()
{
var decoder = new LegacySkinDecoder();
using (var resStream = TestResources.OpenResource("skin.ini"))
using (var stream = new LineBufferedReader(resStream))
{
var config = decoder.Decode(stream);
Assert.AreEqual("test skin", config.SkinInfo.Name);
Assert.AreEqual("TestValue", config.ConfigDictionary["TestLookup"]);
}
}
}
}

View File

@ -0,0 +1,158 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Textures;
using osu.Framework.Testing;
using osu.Game.Audio;
using osu.Game.Skinning;
using osu.Game.Tests.Visual;
using osuTK.Graphics;
namespace osu.Game.Tests.Skins
{
[TestFixture]
[HeadlessTest]
public class TestSceneSkinConfigurationLookup : OsuTestScene
{
private LegacySkin source1;
private LegacySkin source2;
private SkinRequester requester;
[SetUp]
public void SetUp() => Schedule(() =>
{
Add(new SkinProvidingContainer(source1 = new SkinSource())
.WithChild(new SkinProvidingContainer(source2 = new SkinSource())
.WithChild(requester = new SkinRequester())));
});
[Test]
public void TestBasicLookup()
{
AddStep("Add config values", () =>
{
source1.Configuration.ConfigDictionary["Lookup"] = "source1";
source2.Configuration.ConfigDictionary["Lookup"] = "source2";
});
AddAssert("Check lookup finds source2", () => requester.GetConfig<string, string>("Lookup")?.Value == "source2");
}
[Test]
public void TestFloatLookup()
{
AddStep("Add config values", () => source1.Configuration.ConfigDictionary["FloatTest"] = "1.1");
AddAssert("Check float parse lookup", () => requester.GetConfig<string, float>("FloatTest")?.Value == 1.1f);
}
[Test]
public void TestBoolLookup()
{
AddStep("Add config values", () => source1.Configuration.ConfigDictionary["BoolTest"] = "1");
AddAssert("Check bool parse lookup", () => requester.GetConfig<string, bool>("BoolTest")?.Value == true);
}
[Test]
public void TestEnumLookup()
{
AddStep("Add config values", () => source1.Configuration.ConfigDictionary["Test"] = "Test2");
AddAssert("Check enum parse lookup", () => requester.GetConfig<LookupType, ValueType>(LookupType.Test)?.Value == ValueType.Test2);
}
[Test]
public void TestLookupFailure()
{
AddAssert("Check lookup failure", () => requester.GetConfig<string, float>("Lookup") == null);
}
[Test]
public void TestLookupNull()
{
AddStep("Add config values", () => source1.Configuration.ConfigDictionary["Lookup"] = null);
AddAssert("Check lookup null", () =>
{
var bindable = requester.GetConfig<string, string>("Lookup");
return bindable != null && bindable.Value == null;
});
}
[Test]
public void TestColourLookup()
{
AddStep("Add config colour", () => source1.Configuration.CustomColours["Lookup"] = Color4.Red);
AddAssert("Check colour lookup", () => requester.GetConfig<SkinCustomColourLookup, Color4>(new SkinCustomColourLookup("Lookup"))?.Value == Color4.Red);
}
[Test]
public void TestGlobalLookup()
{
AddAssert("Check combo colours", () => requester.GetConfig<GlobalSkinConfiguration, List<Color4>>(GlobalSkinConfiguration.ComboColours)?.Value?.Count > 0);
}
[Test]
public void TestWrongColourType()
{
AddStep("Add config colour", () => source1.Configuration.CustomColours["Lookup"] = Color4.Red);
AddAssert("perform incorrect lookup", () =>
{
try
{
requester.GetConfig<SkinCustomColourLookup, int>(new SkinCustomColourLookup("Lookup"));
return false;
}
catch
{
return true;
}
});
}
public enum LookupType
{
Test
}
public enum ValueType
{
Test1,
Test2,
Test3
}
public class SkinSource : LegacySkin
{
public SkinSource()
: base(new SkinInfo(), null, null, string.Empty)
{
}
}
public class SkinRequester : Drawable, ISkin
{
private ISkinSource skin;
[BackgroundDependencyLoader]
private void load(ISkinSource skin)
{
this.skin = skin;
}
public Drawable GetDrawableComponent(ISkinComponent component) => skin.GetDrawableComponent(component);
public Texture GetTexture(string componentName) => skin.GetTexture(componentName);
public SampleChannel GetSample(ISampleInfo sampleInfo) => skin.GetSample(sampleInfo);
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => skin.GetConfig<TLookup, TValue>(lookup);
}
}
}

View File

@ -7,15 +7,16 @@ using System.Linq;
using System.Threading;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Events;
using osu.Framework.Input.States;
using osu.Framework.Platform;
using osu.Framework.Screens;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Database;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
@ -35,7 +36,7 @@ using osuTK.Graphics;
namespace osu.Game.Tests.Visual.Background
{
[TestFixture]
public class TestSceneBackgroundScreenBeatmap : ManualInputManagerTestScene
public class TestSceneUserDimContainer : ManualInputManagerTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
@ -49,29 +50,17 @@ namespace osu.Game.Tests.Visual.Background
private DummySongSelect songSelect;
private TestPlayerLoader playerLoader;
private TestPlayer player;
private DatabaseContextFactory factory;
private BeatmapManager manager;
private RulesetStore rulesets;
[BackgroundDependencyLoader]
private void load(GameHost host)
private void load(GameHost host, AudioManager audio)
{
factory = new DatabaseContextFactory(LocalStorage);
factory.ResetDatabase();
using (var usage = factory.Get())
usage.Migrate();
factory.ResetDatabase();
using (var usage = factory.Get())
usage.Migrate();
Dependencies.Cache(rulesets = new RulesetStore(factory));
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, factory, rulesets, null, null, host, Beatmap.Default));
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, host, Beatmap.Default));
Dependencies.Cache(new OsuConfigManager(LocalStorage));
manager.Import(TestResources.GetTestBeatmapForImport());
manager.Import(TestResources.GetTestBeatmapForImport()).Wait();
Beatmap.SetDefault();
}
@ -130,20 +119,20 @@ namespace osu.Game.Tests.Visual.Background
{
performFullSetup();
createFakeStoryboard();
AddStep("Storyboard Enabled", () =>
AddStep("Enable Storyboard", () =>
{
player.ReplacesBackground.Value = true;
player.StoryboardEnabled.Value = true;
});
waitForDim();
AddAssert("Background is invisible, storyboard is visible", () => songSelect.IsBackgroundInvisible() && player.IsStoryboardVisible());
AddStep("Storyboard Disabled", () =>
AddAssert("Background is invisible, storyboard is visible", () => songSelect.IsBackgroundInvisible() && player.IsStoryboardVisible);
AddStep("Disable Storyboard", () =>
{
player.ReplacesBackground.Value = false;
player.StoryboardEnabled.Value = false;
});
waitForDim();
AddAssert("Background is visible, storyboard is invisible", () => songSelect.IsBackgroundVisible() && player.IsStoryboardInvisible());
AddAssert("Background is visible, storyboard is invisible", () => songSelect.IsBackgroundVisible() && !player.IsStoryboardVisible);
}
/// <summary>
@ -160,22 +149,44 @@ namespace osu.Game.Tests.Visual.Background
}
/// <summary>
/// Check if the <see cref="UserDimContainer"/> is properly accepting user-defined visual changes at all.
/// Ensure <see cref="UserDimContainer"/> is properly accepting user-defined visual changes for a background.
/// </summary>
[Test]
public void DisableUserDimTest()
public void DisableUserDimBackgroundTest()
{
performFullSetup();
waitForDim();
AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
AddStep("EnableUserDim disabled", () => songSelect.DimEnabled.Value = false);
AddStep("Enable user dim", () => songSelect.DimEnabled.Value = false);
waitForDim();
AddAssert("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && songSelect.IsUserBlurDisabled());
AddStep("EnableUserDim enabled", () => songSelect.DimEnabled.Value = true);
AddStep("Disable user dim", () => songSelect.DimEnabled.Value = true);
waitForDim();
AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
}
/// <summary>
/// Ensure <see cref="UserDimContainer"/> is properly accepting user-defined visual changes for a storyboard.
/// </summary>
[Test]
public void DisableUserDimStoryboardTest()
{
performFullSetup();
createFakeStoryboard();
AddStep("Enable Storyboard", () =>
{
player.ReplacesBackground.Value = true;
player.StoryboardEnabled.Value = true;
});
AddStep("Enable user dim", () => player.DimmableStoryboard.EnableUserDim.Value = true);
AddStep("Set dim level to 1", () => songSelect.DimLevel.Value = 1f);
waitForDim();
AddAssert("Storyboard is invisible", () => !player.IsStoryboardVisible);
AddStep("Disable user dim", () => player.DimmableStoryboard.EnableUserDim.Value = false);
waitForDim();
AddAssert("Storyboard is visible", () => player.IsStoryboardVisible);
}
/// <summary>
/// Check if the visual settings container retains dim and blur when pausing
/// </summary>
@ -240,14 +251,15 @@ namespace osu.Game.Tests.Visual.Background
{
player.StoryboardEnabled.Value = false;
player.ReplacesBackground.Value = false;
player.CurrentStoryboardContainer.Add(new OsuSpriteText
player.DimmableStoryboard.Add(new OsuSpriteText
{
Size = new Vector2(250, 50),
Size = new Vector2(500, 50),
Alpha = 1,
Colour = Color4.Tomato,
Colour = Color4.White,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Text = "THIS IS A STORYBOARD",
Font = new FontUsage(size: 50)
});
});
@ -273,6 +285,12 @@ namespace osu.Game.Tests.Visual.Background
});
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
rulesets?.Dispose();
}
private class DummySongSelect : PlaySongSelect
{
protected override BackgroundScreen CreateBackground()
@ -299,7 +317,7 @@ namespace osu.Game.Tests.Visual.Background
public bool IsBackgroundUndimmed() => ((FadeAccessibleBackground)Background).CurrentColour == Color4.White;
public bool IsUserBlurApplied() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2((float)BlurLevel.Value * 25);
public bool IsUserBlurApplied() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2((float)BlurLevel.Value * BackgroundScreenBeatmap.USER_BLUR_FACTOR);
public bool IsUserBlurDisabled() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2(0);
@ -332,17 +350,7 @@ namespace osu.Game.Tests.Visual.Background
{
protected override BackgroundScreen CreateBackground() => new FadeAccessibleBackground(Beatmap.Value);
protected override UserDimContainer CreateStoryboardContainer()
{
return new TestUserDimContainer(true)
{
RelativeSizeAxes = Axes.Both,
Alpha = 1,
EnableUserDim = { Value = true }
};
}
public UserDimContainer CurrentStoryboardContainer => StoryboardContainer;
public new DimmableStoryboard DimmableStoryboard => base.DimmableStoryboard;
// Whether or not the player should be allowed to load.
public bool BlockLoad;
@ -356,9 +364,7 @@ namespace osu.Game.Tests.Visual.Background
{
}
public bool IsStoryboardVisible() => ((TestUserDimContainer)CurrentStoryboardContainer).CurrentAlpha == 1;
public bool IsStoryboardInvisible() => ((TestUserDimContainer)CurrentStoryboardContainer).CurrentAlpha <= 1;
public bool IsStoryboardVisible => DimmableStoryboard.ContentDisplayed;
[BackgroundDependencyLoader]
private void load(OsuConfigManager config, CancellationToken token)
@ -391,15 +397,15 @@ namespace osu.Game.Tests.Visual.Background
private class FadeAccessibleBackground : BackgroundScreenBeatmap
{
protected override UserDimContainer CreateFadeContainer() => fadeContainer = new TestUserDimContainer { RelativeSizeAxes = Axes.Both };
protected override DimmableBackground CreateFadeContainer() => dimmable = new TestDimmableBackground { RelativeSizeAxes = Axes.Both };
public Color4 CurrentColour => fadeContainer.CurrentColour;
public Color4 CurrentColour => dimmable.CurrentColour;
public float CurrentAlpha => fadeContainer.CurrentAlpha;
public float CurrentAlpha => dimmable.CurrentAlpha;
public Vector2 CurrentBlur => Background.BlurSigma;
private TestUserDimContainer fadeContainer;
private TestDimmableBackground dimmable;
public FadeAccessibleBackground(WorkingBeatmap beatmap)
: base(beatmap)
@ -407,15 +413,10 @@ namespace osu.Game.Tests.Visual.Background
}
}
private class TestUserDimContainer : UserDimContainer
private class TestDimmableBackground : BackgroundScreenBeatmap.DimmableBackground
{
public Color4 CurrentColour => DimContainer.Colour;
public float CurrentAlpha => DimContainer.Alpha;
public TestUserDimContainer(bool isStoryboard = false)
: base(isStoryboard)
{
}
public Color4 CurrentColour => Content.Colour;
public float CurrentAlpha => Content.Alpha;
}
}
}

View File

@ -14,94 +14,132 @@ namespace osu.Game.Tests.Visual.Components
[TestFixture]
public class TestSceneIdleTracker : ManualInputManagerTestScene
{
private readonly IdleTrackingBox box1;
private readonly IdleTrackingBox box2;
private readonly IdleTrackingBox box3;
private readonly IdleTrackingBox box4;
private IdleTrackingBox box1;
private IdleTrackingBox box2;
private IdleTrackingBox box3;
private IdleTrackingBox box4;
public TestSceneIdleTracker()
private IdleTrackingBox[] boxes;
[SetUp]
public void SetUp() => Schedule(() =>
{
Children = new Drawable[]
InputManager.MoveMouseTo(Vector2.Zero);
Children = boxes = new[]
{
box1 = new IdleTrackingBox(1000)
box1 = new IdleTrackingBox(2000)
{
Name = "TopLeft",
RelativeSizeAxes = Axes.Both,
Colour = Color4.Red,
Anchor = Anchor.TopLeft,
Origin = Anchor.TopLeft,
},
box2 = new IdleTrackingBox(2000)
box2 = new IdleTrackingBox(4000)
{
Name = "TopRight",
RelativeSizeAxes = Axes.Both,
Colour = Color4.Green,
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
},
box3 = new IdleTrackingBox(3000)
box3 = new IdleTrackingBox(6000)
{
Name = "BottomLeft",
RelativeSizeAxes = Axes.Both,
Colour = Color4.Blue,
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
},
box4 = new IdleTrackingBox(4000)
box4 = new IdleTrackingBox(8000)
{
Name = "BottomRight",
RelativeSizeAxes = Axes.Both,
Colour = Color4.Orange,
Anchor = Anchor.BottomRight,
Origin = Anchor.BottomRight,
},
};
}
});
[Test]
public void TestNudge()
{
AddStep("move mouse to top left", () => InputManager.MoveMouseTo(box1.ScreenSpaceDrawQuad.Centre));
AddStep("move to top left", () => InputManager.MoveMouseTo(box1));
AddUntilStep("Wait for all idle", () => box1.IsIdle && box2.IsIdle && box3.IsIdle && box4.IsIdle);
waitForAllIdle();
AddStep("nudge mouse", () => InputManager.MoveMouseTo(box1.ScreenSpaceDrawQuad.Centre + new Vector2(1)));
AddAssert("check not idle", () => !box1.IsIdle);
AddAssert("check idle", () => box2.IsIdle);
AddAssert("check idle", () => box3.IsIdle);
AddAssert("check idle", () => box4.IsIdle);
checkIdleStatus(1, false);
checkIdleStatus(2, true);
checkIdleStatus(3, true);
checkIdleStatus(4, true);
}
[Test]
public void TestMovement()
{
AddStep("move mouse", () => InputManager.MoveMouseTo(box2.ScreenSpaceDrawQuad.Centre));
AddStep("move to top right", () => InputManager.MoveMouseTo(box2));
AddAssert("check not idle", () => box1.IsIdle);
AddAssert("check not idle", () => !box2.IsIdle);
AddAssert("check idle", () => box3.IsIdle);
AddAssert("check idle", () => box4.IsIdle);
checkIdleStatus(1, true);
checkIdleStatus(2, false);
checkIdleStatus(3, true);
checkIdleStatus(4, true);
AddStep("move mouse", () => InputManager.MoveMouseTo(box3.ScreenSpaceDrawQuad.Centre));
AddStep("move mouse", () => InputManager.MoveMouseTo(box4.ScreenSpaceDrawQuad.Centre));
AddStep("move to bottom left", () => InputManager.MoveMouseTo(box3));
AddStep("move to bottom right", () => InputManager.MoveMouseTo(box4));
AddAssert("check not idle", () => box1.IsIdle);
AddAssert("check not idle", () => !box2.IsIdle);
AddAssert("check idle", () => !box3.IsIdle);
AddAssert("check idle", () => !box4.IsIdle);
checkIdleStatus(1, true);
checkIdleStatus(2, false);
checkIdleStatus(3, false);
checkIdleStatus(4, false);
AddUntilStep("Wait for all idle", () => box1.IsIdle && box2.IsIdle && box3.IsIdle && box4.IsIdle);
waitForAllIdle();
}
[Test]
public void TestTimings()
{
AddStep("move mouse", () => InputManager.MoveMouseTo(ScreenSpaceDrawQuad.Centre));
AddStep("move to centre", () => InputManager.MoveMouseTo(Content));
checkIdleStatus(1, false);
checkIdleStatus(2, false);
checkIdleStatus(3, false);
checkIdleStatus(4, false);
AddAssert("check not idle", () => !box1.IsIdle && !box2.IsIdle && !box3.IsIdle && !box4.IsIdle);
AddUntilStep("Wait for idle", () => box1.IsIdle);
AddAssert("check not idle", () => !box2.IsIdle && !box3.IsIdle && !box4.IsIdle);
checkIdleStatus(1, true);
checkIdleStatus(2, false);
checkIdleStatus(3, false);
checkIdleStatus(4, false);
AddUntilStep("Wait for idle", () => box2.IsIdle);
AddAssert("check not idle", () => !box3.IsIdle && !box4.IsIdle);
checkIdleStatus(1, true);
checkIdleStatus(2, true);
checkIdleStatus(3, false);
checkIdleStatus(4, false);
AddUntilStep("Wait for idle", () => box3.IsIdle);
checkIdleStatus(1, true);
checkIdleStatus(2, true);
checkIdleStatus(3, true);
checkIdleStatus(4, false);
waitForAllIdle();
}
private void checkIdleStatus(int box, bool expectedIdle)
{
AddAssert($"box {box} is {(expectedIdle ? "idle" : "active")}", () => boxes[box - 1].IsIdle == expectedIdle);
}
private void waitForAllIdle()
{
AddUntilStep("Wait for all idle", () => box1.IsIdle && box2.IsIdle && box3.IsIdle && box4.IsIdle);
}

View File

@ -7,6 +7,7 @@ using osu.Framework.Audio.Track;
using osu.Framework.Graphics.Containers;
using osu.Game.Audio;
using osu.Game.Beatmaps;
using static osu.Game.Tests.Visual.Components.TestScenePreviewTrackManager.TestPreviewTrackManager;
namespace osu.Game.Tests.Visual.Components
{
@ -34,6 +35,7 @@ namespace osu.Game.Tests.Visual.Components
PreviewTrack track = null;
AddStep("get track", () => track = getOwnedTrack());
AddUntilStep("wait loaded", () => track.IsLoaded);
AddStep("start", () => track.Start());
AddAssert("started", () => track.IsRunning);
AddStep("stop", () => track.Stop());
@ -52,10 +54,15 @@ namespace osu.Game.Tests.Visual.Components
track2 = getOwnedTrack();
});
AddUntilStep("wait loaded", () => track1.IsLoaded && track2.IsLoaded);
AddStep("start track 1", () => track1.Start());
AddStep("start track 2", () => track2.Start());
AddAssert("track 1 stopped", () => !track1.IsRunning);
AddAssert("track 2 started", () => track2.IsRunning);
AddStep("start track 1", () => track1.Start());
AddAssert("track 2 stopped", () => !track2.IsRunning);
AddAssert("track 1 started", () => track1.IsRunning);
}
[Test]
@ -64,6 +71,7 @@ namespace osu.Game.Tests.Visual.Components
PreviewTrack track = null;
AddStep("get track", () => track = getOwnedTrack());
AddUntilStep("wait loaded", () => track.IsLoaded);
AddStep("start", () => track.Start());
AddStep("stop by owner", () => trackManager.StopAnyPlaying(this));
AddAssert("stopped", () => !track.IsRunning);
@ -76,6 +84,7 @@ namespace osu.Game.Tests.Visual.Components
PreviewTrack track = null;
AddStep("get track", () => Add(owner = new TestTrackOwner(track = getTrack())));
AddUntilStep("wait loaded", () => track.IsLoaded);
AddStep("start", () => track.Start());
AddStep("attempt stop", () => trackManager.StopAnyPlaying(this));
AddAssert("not stopped", () => track.IsRunning);
@ -83,22 +92,46 @@ namespace osu.Game.Tests.Visual.Components
AddAssert("stopped", () => !track.IsRunning);
}
private PreviewTrack getTrack() => trackManager.Get(null);
[Test]
public void TestNonPresentTrack()
{
TestPreviewTrack track = null;
private PreviewTrack getOwnedTrack()
AddStep("get non-present track", () =>
{
Add(new TestTrackOwner(track = getTrack()));
track.Alpha = 0;
});
AddUntilStep("wait loaded", () => track.IsLoaded);
AddStep("start", () => track.Start());
AddStep("seek to end", () => track.Track.Seek(track.Track.Length));
AddAssert("track stopped", () => !track.IsRunning);
}
private TestPreviewTrack getTrack() => (TestPreviewTrack)trackManager.Get(null);
private TestPreviewTrack getOwnedTrack()
{
var track = getTrack();
Add(track);
LoadComponentAsync(track, Add);
return track;
}
private class TestTrackOwner : CompositeDrawable, IPreviewTrackOwner
{
private readonly PreviewTrack track;
public TestTrackOwner(PreviewTrack track)
{
AddInternal(track);
this.track = track;
}
[BackgroundDependencyLoader]
private void load()
{
LoadComponentAsync(track, AddInternal);
}
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
@ -109,18 +142,23 @@ namespace osu.Game.Tests.Visual.Components
}
}
private class TestPreviewTrackManager : PreviewTrackManager
public class TestPreviewTrackManager : PreviewTrackManager
{
protected override TrackManagerPreviewTrack CreatePreviewTrack(BeatmapSetInfo beatmapSetInfo, TrackManager trackManager) => new TestPreviewTrack(beatmapSetInfo, trackManager);
protected override TrackManagerPreviewTrack CreatePreviewTrack(BeatmapSetInfo beatmapSetInfo, ITrackStore trackStore) => new TestPreviewTrack(beatmapSetInfo, trackStore);
protected class TestPreviewTrack : TrackManagerPreviewTrack
public class TestPreviewTrack : TrackManagerPreviewTrack
{
public TestPreviewTrack(BeatmapSetInfo beatmapSetInfo, TrackManager trackManager)
private readonly ITrackStore trackManager;
public new Track Track => base.Track;
public TestPreviewTrack(BeatmapSetInfo beatmapSetInfo, ITrackStore trackManager)
: base(beatmapSetInfo, trackManager)
{
this.trackManager = trackManager;
}
protected override Track GetTrack() => new TrackVirtual { Length = 100000 };
protected override Track GetTrack() => trackManager.GetVirtual(100000);
}
}
}

View File

@ -1,25 +1,20 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Edit.Compose;
using osu.Game.Tests.Beatmaps;
namespace osu.Game.Tests.Visual.Editor
{
[TestFixture]
public class TestSceneEditorCompose : EditorClockTestScene
public class TestSceneComposeScreen : EditorClockTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[] { typeof(ComposeScreen) };
[BackgroundDependencyLoader]
private void load()
{
Beatmap.Value = new TestWorkingBeatmap(new OsuRuleset().RulesetInfo, Clock);
Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
Child = new ComposeScreen();
}
}

View File

@ -0,0 +1,171 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Compose.Components;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Tests.Visual.Editor
{
public class TestSceneDistanceSnapGrid : EditorClockTestScene
{
private const double beat_length = 100;
private static readonly Vector2 grid_position = new Vector2(512, 384);
[Cached(typeof(IEditorBeatmap))]
private readonly EditorBeatmap<OsuHitObject> editorBeatmap;
[Cached(typeof(IDistanceSnapProvider))]
private readonly SnapProvider snapProvider = new SnapProvider();
public TestSceneDistanceSnapGrid()
{
editorBeatmap = new EditorBeatmap<OsuHitObject>(new OsuBeatmap());
editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = beat_length });
}
[SetUp]
public void Setup() => Schedule(() =>
{
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.SlateGray
},
new TestDistanceSnapGrid(new HitObject(), grid_position)
};
});
[TestCase(1)]
[TestCase(2)]
[TestCase(3)]
[TestCase(4)]
[TestCase(6)]
[TestCase(8)]
[TestCase(12)]
[TestCase(16)]
public void TestBeatDivisor(int divisor)
{
AddStep($"set beat divisor = {divisor}", () => BeatDivisor.Value = divisor);
}
[Test]
public void TestLimitedDistance()
{
AddStep("create limited grid", () =>
{
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.SlateGray
},
new TestDistanceSnapGrid(new HitObject(), grid_position, new HitObject { StartTime = 100 })
};
});
}
private class TestDistanceSnapGrid : DistanceSnapGrid
{
public new float DistanceSpacing => base.DistanceSpacing;
public TestDistanceSnapGrid(HitObject hitObject, Vector2 centrePosition, HitObject nextHitObject = null)
: base(hitObject, nextHitObject, centrePosition)
{
}
protected override void CreateContent(Vector2 centrePosition)
{
AddInternal(new Circle
{
Origin = Anchor.Centre,
Size = new Vector2(5),
Position = centrePosition
});
int beatIndex = 0;
for (float s = centrePosition.X + DistanceSpacing; s <= DrawWidth && beatIndex < MaxIntervals; s += DistanceSpacing, beatIndex++)
{
AddInternal(new Circle
{
Origin = Anchor.Centre,
Size = new Vector2(5, 10),
Position = new Vector2(s, centrePosition.Y),
Colour = GetColourForBeatIndex(beatIndex)
});
}
beatIndex = 0;
for (float s = centrePosition.X - DistanceSpacing; s >= 0 && beatIndex < MaxIntervals; s -= DistanceSpacing, beatIndex++)
{
AddInternal(new Circle
{
Origin = Anchor.Centre,
Size = new Vector2(5, 10),
Position = new Vector2(s, centrePosition.Y),
Colour = GetColourForBeatIndex(beatIndex)
});
}
beatIndex = 0;
for (float s = centrePosition.Y + DistanceSpacing; s <= DrawHeight && beatIndex < MaxIntervals; s += DistanceSpacing, beatIndex++)
{
AddInternal(new Circle
{
Origin = Anchor.Centre,
Size = new Vector2(10, 5),
Position = new Vector2(centrePosition.X, s),
Colour = GetColourForBeatIndex(beatIndex)
});
}
beatIndex = 0;
for (float s = centrePosition.Y - DistanceSpacing; s >= 0 && beatIndex < MaxIntervals; s -= DistanceSpacing, beatIndex++)
{
AddInternal(new Circle
{
Origin = Anchor.Centre,
Size = new Vector2(10, 5),
Position = new Vector2(centrePosition.X, s),
Colour = GetColourForBeatIndex(beatIndex)
});
}
}
public override (Vector2 position, double time) GetSnappedPosition(Vector2 screenSpacePosition)
=> (Vector2.Zero, 0);
}
private class SnapProvider : IDistanceSnapProvider
{
public (Vector2 position, double time) GetSnappedPosition(Vector2 position, double time) => (position, time);
public float GetBeatSnapDistanceAt(double referenceTime) => 10;
public float DurationToDistance(double referenceTime, double duration) => (float)duration;
public double DistanceToDuration(double referenceTime, float distance) => distance;
public double GetSnappedDurationFromDistance(double referenceTime, float distance) => 0;
public float GetSnappedDistanceFromDistance(double referenceTime, float distance) => 0;
}
}
}

View File

@ -5,13 +5,14 @@ using System;
using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Graphics.UserInterface;
using osu.Game.Screens.Edit.Compose.Components.Timeline;
using osuTK;
using osuTK.Graphics;
@ -30,9 +31,9 @@ namespace osu.Game.Tests.Visual.Editor
};
[BackgroundDependencyLoader]
private void load()
private void load(AudioManager audio)
{
Beatmap.Value = new WaveformTestBeatmap();
Beatmap.Value = new WaveformTestBeatmap(audio);
Children = new Drawable[]
{
@ -100,7 +101,7 @@ namespace osu.Game.Tests.Visual.Editor
}
}
private class StartStopButton : Button
private class StartStopButton : OsuButton
{
private IAdjustableClock adjustableClock;
private bool started;

View File

@ -1,4 +1,4 @@
// 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 NUnit.Framework;
@ -10,7 +10,6 @@ using osu.Framework.Graphics.Shapes;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Tests.Beatmaps;
using osuTK;
using osuTK.Graphics;
@ -29,18 +28,7 @@ namespace osu.Game.Tests.Visual.Editor
{
var testBeatmap = new Beatmap
{
ControlPointInfo = new ControlPointInfo
{
TimingPoints =
{
new TimingControlPoint { Time = 0, BeatLength = 200 },
new TimingControlPoint { Time = 100, BeatLength = 400 },
new TimingControlPoint { Time = 175, BeatLength = 800 },
new TimingControlPoint { Time = 350, BeatLength = 200 },
new TimingControlPoint { Time = 450, BeatLength = 100 },
new TimingControlPoint { Time = 500, BeatLength = 307.69230769230802 }
}
},
ControlPointInfo = new ControlPointInfo(),
HitObjects =
{
new HitCircle { StartTime = 0 },
@ -48,7 +36,14 @@ namespace osu.Game.Tests.Visual.Editor
}
};
Beatmap.Value = new TestWorkingBeatmap(testBeatmap, Clock);
testBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 200 });
testBeatmap.ControlPointInfo.Add(100, new TimingControlPoint { BeatLength = 400 });
testBeatmap.ControlPointInfo.Add(175, new TimingControlPoint { BeatLength = 800 });
testBeatmap.ControlPointInfo.Add(350, new TimingControlPoint { BeatLength = 200 });
testBeatmap.ControlPointInfo.Add(450, new TimingControlPoint { BeatLength = 100 });
testBeatmap.ControlPointInfo.Add(500, new TimingControlPoint { BeatLength = 307.69230769230802 });
Beatmap.Value = CreateWorkingBeatmap(testBeatmap);
Child = new TimingPointVisualiser(testBeatmap, 5000) { Clock = Clock };
}

View File

@ -8,7 +8,6 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Edit.Components.Timelines.Summary;
using osu.Game.Tests.Beatmaps;
using osuTK;
namespace osu.Game.Tests.Visual.Editor
@ -21,7 +20,7 @@ namespace osu.Game.Tests.Visual.Editor
[BackgroundDependencyLoader]
private void load()
{
Beatmap.Value = new TestWorkingBeatmap(new OsuRuleset().RulesetInfo, null);
Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
Add(new SummaryTimeline
{

View File

@ -16,16 +16,13 @@ using osu.Game.Rulesets.Osu.Edit;
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles;
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit.Compose;
using osu.Game.Screens.Edit.Compose.Components;
using osu.Game.Tests.Beatmaps;
using osuTK;
namespace osu.Game.Tests.Visual.Editor
{
[TestFixture]
[Cached(Type = typeof(IPlacementHandler))]
public class TestSceneHitObjectComposer : OsuTestScene, IPlacementHandler
public class TestSceneHitObjectComposer : EditorClockTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
@ -40,12 +37,10 @@ namespace osu.Game.Tests.Visual.Editor
typeof(HitCirclePlacementBlueprint),
};
private HitObjectComposer composer;
[BackgroundDependencyLoader]
private void load()
{
Beatmap.Value = new TestWorkingBeatmap(new Beatmap
Beatmap.Value = CreateWorkingBeatmap(new Beatmap
{
HitObjects = new List<HitObject>
{
@ -68,15 +63,7 @@ namespace osu.Game.Tests.Visual.Editor
Dependencies.CacheAs<IAdjustableClock>(clock);
Dependencies.CacheAs<IFrameBasedClock>(clock);
Child = composer = new OsuHitObjectComposer(new OsuRuleset());
Child = new OsuHitObjectComposer(new OsuRuleset());
}
public void BeginPlacement(HitObject hitObject)
{
}
public void EndPlacement(HitObject hitObject) => composer.Add(hitObject);
public void Delete(HitObject hitObject) => composer.Remove(hitObject);
}
}

View File

@ -1,4 +1,4 @@
// 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 NUnit.Framework;
@ -7,7 +7,6 @@ using osu.Framework.Graphics;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Screens.Edit.Components;
using osu.Game.Tests.Beatmaps;
using osuTK;
namespace osu.Game.Tests.Visual.Editor
@ -29,7 +28,7 @@ namespace osu.Game.Tests.Visual.Editor
Size = new Vector2(200, 100)
};
Beatmap.Value = new TestWorkingBeatmap(new Beatmap(), Clock);
Beatmap.Value = CreateWorkingBeatmap(new Beatmap());
Child = playback;
}

View File

@ -0,0 +1,35 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Edit.Timing;
namespace osu.Game.Tests.Visual.Editor
{
[TestFixture]
public class TestSceneTimingScreen : EditorClockTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(ControlPointTable),
typeof(ControlPointSettings),
typeof(Section<>),
typeof(TimingSection),
typeof(EffectSection),
typeof(SampleSection),
typeof(DifficultySection),
typeof(RowAttribute)
};
[BackgroundDependencyLoader]
private void load()
{
Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
Child = new TimingScreen();
}
}
}

View File

@ -3,6 +3,7 @@
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Track;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Audio;
@ -10,6 +11,7 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Beatmaps;
using osu.Game.Graphics.Sprites;
using osu.Game.Rulesets.Osu;
using osuTK.Graphics;
namespace osu.Game.Tests.Visual.Editor
@ -20,9 +22,9 @@ namespace osu.Game.Tests.Visual.Editor
private WorkingBeatmap waveformBeatmap;
[BackgroundDependencyLoader]
private void load()
private void load(AudioManager audio)
{
waveformBeatmap = new WaveformTestBeatmap();
waveformBeatmap = new WaveformTestBeatmap(audio);
}
[TestCase(1f)]
@ -91,7 +93,7 @@ namespace osu.Game.Tests.Visual.Editor
Child = graph = new TestWaveformGraph
{
RelativeSizeAxes = Axes.Both,
Waveform = new DummyWorkingBeatmap().Waveform,
Waveform = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo).Waveform,
},
};
});

View File

@ -50,7 +50,7 @@ namespace osu.Game.Tests.Visual.Gameplay
private void loadPlayerWithBeatmap(IBeatmap beatmap)
{
Beatmap.Value = new TestWorkingBeatmap(beatmap, Clock);
Beatmap.Value = new TestWorkingBeatmap(beatmap);
LoadScreen(player = new LeadInPlayer());
}

View File

@ -3,6 +3,7 @@
using System.ComponentModel;
using System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Play;
@ -12,6 +13,8 @@ namespace osu.Game.Tests.Visual.Gameplay
[Description("Player instantiated with an autoplay mod.")]
public class TestSceneAutoplay : AllPlayersTestScene
{
private ClockBackedTestWorkingBeatmap.TrackVirtualManual track;
protected override Player CreatePlayer(Ruleset ruleset)
{
Mods.Value = Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() }).ToArray();
@ -21,7 +24,18 @@ namespace osu.Game.Tests.Visual.Gameplay
protected override void AddCheckSteps()
{
AddUntilStep("score above zero", () => ((ScoreAccessiblePlayer)Player).ScoreProcessor.TotalScore.Value > 0);
AddUntilStep("key counter counted keys", () => ((ScoreAccessiblePlayer)Player).HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 0));
AddUntilStep("key counter counted keys", () => ((ScoreAccessiblePlayer)Player).HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 2));
AddStep("rewind", () => track.Seek(-10000));
AddUntilStep("key counter reset", () => ((ScoreAccessiblePlayer)Player).HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses == 0));
}
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap)
{
var working = base.CreateWorkingBeatmap(beatmap);
track = (ClockBackedTestWorkingBeatmap.TrackVirtualManual)working.Track;
return working;
}
private class ScoreAccessiblePlayer : TestPlayer
@ -29,6 +43,8 @@ namespace osu.Game.Tests.Visual.Gameplay
public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
public new HUDOverlay HUDOverlay => base.HUDOverlay;
public new GameplayClockContainer GameplayClockContainer => base.GameplayClockContainer;
public ScoreAccessiblePlayer()
: base(false, false)
{

View File

@ -0,0 +1,119 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
using osu.Game.Rulesets.Objects;
using System;
using System.Collections.Generic;
using osu.Game.Rulesets.Judgements;
using osu.Framework.MathUtils;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Game.Rulesets.Catch.Scoring;
using osu.Game.Rulesets.Mania.Scoring;
using osu.Game.Rulesets.Osu.Scoring;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Scoring;
using osu.Game.Screens.Play.HUD.HitErrorMeters;
namespace osu.Game.Tests.Visual.Gameplay
{
public class TestSceneBarHitErrorMeter : OsuTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(HitErrorMeter),
};
private HitErrorMeter meter;
private HitErrorMeter meter2;
private HitWindows hitWindows;
public TestSceneBarHitErrorMeter()
{
recreateDisplay(new OsuHitWindows(), 5);
AddRepeatStep("New random judgement", () => newJudgement(), 40);
AddRepeatStep("New max negative", () => newJudgement(-hitWindows.WindowFor(HitResult.Meh)), 20);
AddRepeatStep("New max positive", () => newJudgement(hitWindows.WindowFor(HitResult.Meh)), 20);
AddStep("New fixed judgement (50ms)", () => newJudgement(50));
}
[Test]
public void TestOsu()
{
AddStep("OD 1", () => recreateDisplay(new OsuHitWindows(), 1));
AddStep("OD 10", () => recreateDisplay(new OsuHitWindows(), 10));
}
[Test]
public void TestTaiko()
{
AddStep("OD 1", () => recreateDisplay(new TaikoHitWindows(), 1));
AddStep("OD 10", () => recreateDisplay(new TaikoHitWindows(), 10));
}
[Test]
public void TestMania()
{
AddStep("OD 1", () => recreateDisplay(new ManiaHitWindows(), 1));
AddStep("OD 10", () => recreateDisplay(new ManiaHitWindows(), 10));
}
[Test]
public void TestCatch()
{
AddStep("OD 1", () => recreateDisplay(new CatchHitWindows(), 1));
AddStep("OD 10", () => recreateDisplay(new CatchHitWindows(), 10));
}
private void recreateDisplay(HitWindows hitWindows, float overallDifficulty)
{
this.hitWindows = hitWindows;
hitWindows?.SetDifficulty(overallDifficulty);
Clear();
Add(new FillFlowContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Direction = FillDirection.Vertical,
AutoSizeAxes = Axes.Both,
Children = new[]
{
new SpriteText { Text = $@"Great: {hitWindows?.WindowFor(HitResult.Great)}" },
new SpriteText { Text = $@"Good: {hitWindows?.WindowFor(HitResult.Good)}" },
new SpriteText { Text = $@"Meh: {hitWindows?.WindowFor(HitResult.Meh)}" },
}
});
Add(meter = new BarHitErrorMeter(hitWindows, true)
{
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
});
Add(meter2 = new BarHitErrorMeter(hitWindows, false)
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
});
}
private void newJudgement(double offset = 0)
{
var judgement = new JudgementResult(new HitObject(), new Judgement())
{
TimeOffset = offset == 0 ? RNG.Next(-150, 150) : offset,
Type = HitResult.Perfect,
};
meter.OnNewJudgement(judgement);
meter2.OnNewJudgement(judgement);
}
}
}

View File

@ -1,8 +1,11 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Timing;
using osu.Game.Beatmaps.Timing;
using osu.Game.Screens.Play;
@ -11,78 +14,172 @@ namespace osu.Game.Tests.Visual.Gameplay
[TestFixture]
public class TestSceneBreakOverlay : OsuTestScene
{
private readonly BreakOverlay breakOverlay;
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(BreakOverlay),
};
private readonly TestBreakOverlay breakOverlay;
private readonly IReadOnlyList<BreakPeriod> testBreaks = new List<BreakPeriod>
{
new BreakPeriod
{
StartTime = 1000,
EndTime = 5000,
},
new BreakPeriod
{
StartTime = 6000,
EndTime = 13500,
},
};
public TestSceneBreakOverlay()
{
Child = breakOverlay = new BreakOverlay(true);
AddStep("2s break", () => startBreak(2000));
AddStep("5s break", () => startBreak(5000));
AddStep("10s break", () => startBreak(10000));
AddStep("15s break", () => startBreak(15000));
AddStep("2s, 2s", startMultipleBreaks);
AddStep("0.5s, 0.7s, 1s, 2s", startAnotherMultipleBreaks);
Add(breakOverlay = new TestBreakOverlay(true));
}
private void startBreak(double duration)
[Test]
public void TestShowBreaks()
{
breakOverlay.Breaks = new List<BreakPeriod>
setClock(false);
addShowBreakStep(2);
addShowBreakStep(5);
addShowBreakStep(15);
}
[Test]
public void TestNoEffectsBreak()
{
var shortBreak = new BreakPeriod { EndTime = 500 };
setClock(true);
loadBreaksStep("short break", new[] { shortBreak });
addBreakSeeks(shortBreak, false);
}
[Test]
public void TestMultipleBreaks()
{
setClock(true);
loadBreaksStep("multiple breaks", testBreaks);
foreach (var b in testBreaks)
addBreakSeeks(b, false);
}
[Test]
public void TestRewindBreaks()
{
setClock(true);
loadBreaksStep("multiple breaks", testBreaks);
foreach (var b in testBreaks.Reverse())
addBreakSeeks(b, true);
}
[Test]
public void TestSkipBreaks()
{
setClock(true);
loadBreaksStep("multiple breaks", testBreaks);
seekAndAssertBreak("seek to break start", testBreaks[1].StartTime, true);
AddAssert("is skipped to break #2", () => breakOverlay.CurrentBreakIndex == 1);
seekAndAssertBreak("seek to break middle", testBreaks[1].StartTime + testBreaks[1].Duration / 2, true);
seekAndAssertBreak("seek to break end", testBreaks[1].EndTime, false);
seekAndAssertBreak("seek to break after end", testBreaks[1].EndTime + 500, false);
}
private void addShowBreakStep(double seconds)
{
AddStep($"show '{seconds}s' break", () => breakOverlay.Breaks = new List<BreakPeriod>
{
new BreakPeriod
{
StartTime = Clock.CurrentTime,
EndTime = Clock.CurrentTime + duration,
EndTime = Clock.CurrentTime + seconds * 1000,
}
};
});
}
private void startMultipleBreaks()
private void setClock(bool useManual)
{
double currentTime = Clock.CurrentTime;
breakOverlay.Breaks = new List<BreakPeriod>
{
new BreakPeriod
{
StartTime = currentTime,
EndTime = currentTime + 2000,
},
new BreakPeriod
{
StartTime = currentTime + 4000,
EndTime = currentTime + 6000,
}
};
AddStep($"set {(useManual ? "manual" : "realtime")} clock", () => breakOverlay.SwitchClock(useManual));
}
private void startAnotherMultipleBreaks()
private void loadBreaksStep(string breakDescription, IReadOnlyList<BreakPeriod> breaks)
{
double currentTime = Clock.CurrentTime;
AddStep($"load {breakDescription}", () => breakOverlay.Breaks = breaks);
seekAndAssertBreak("seek back to 0", 0, false);
}
breakOverlay.Breaks = new List<BreakPeriod>
private void addBreakSeeks(BreakPeriod b, bool isReversed)
{
if (isReversed)
{
new BreakPeriod // Duration is less than 650 - too short to appear
{
StartTime = currentTime,
EndTime = currentTime + 500,
},
new BreakPeriod
{
StartTime = currentTime + 1500,
EndTime = currentTime + 2200,
},
new BreakPeriod
{
StartTime = currentTime + 3200,
EndTime = currentTime + 4200,
},
new BreakPeriod
{
StartTime = currentTime + 5200,
EndTime = currentTime + 7200,
}
};
seekAndAssertBreak("seek to break after end", b.EndTime + 500, false);
seekAndAssertBreak("seek to break end", b.EndTime, false);
seekAndAssertBreak("seek to break middle", b.StartTime + b.Duration / 2, b.HasEffect);
seekAndAssertBreak("seek to break start", b.StartTime, b.HasEffect);
}
else
{
seekAndAssertBreak("seek to break start", b.StartTime, b.HasEffect);
seekAndAssertBreak("seek to break middle", b.StartTime + b.Duration / 2, b.HasEffect);
seekAndAssertBreak("seek to break end", b.EndTime, false);
seekAndAssertBreak("seek to break after end", b.EndTime + 500, false);
}
}
private void seekAndAssertBreak(string seekStepDescription, double time, bool shouldBeBreak)
{
AddStep(seekStepDescription, () => breakOverlay.ManualClockTime = time);
AddAssert($"is{(!shouldBeBreak ? " not" : string.Empty)} break time", () =>
{
breakOverlay.ProgressTime();
return breakOverlay.IsBreakTime.Value == shouldBeBreak;
});
}
private class TestBreakOverlay : BreakOverlay
{
private readonly FramedClock framedManualClock;
private readonly ManualClock manualClock;
private IFrameBasedClock originalClock;
public new int CurrentBreakIndex => base.CurrentBreakIndex;
public double ManualClockTime
{
get => manualClock.CurrentTime;
set => manualClock.CurrentTime = value;
}
public TestBreakOverlay(bool letterboxing)
: base(letterboxing)
{
framedManualClock = new FramedClock(manualClock = new ManualClock());
ProcessCustomClock = false;
}
public void ProgressTime()
{
framedManualClock.ProcessFrame();
Update();
}
public void SwitchClock(bool setManual) => Clock = setManual ? framedManualClock : originalClock;
protected override void LoadComplete()
{
base.LoadComplete();
originalClock = Clock;
}
}
}
}

View File

@ -0,0 +1,334 @@
// 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.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input;
using osu.Framework.MathUtils;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Configuration;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Tests.Visual.Gameplay
{
public class TestSceneDrawableScrollingRuleset : OsuTestScene
{
/// <summary>
/// The amount of time visible by the "view window" of the playfield.
/// All hitobjects added through <see cref="createBeatmap"/> are spaced apart by this value, such that for a beat length of 1000,
/// there will be at most 2 hitobjects visible in the "view window".
/// </summary>
private const double time_range = 1000;
private readonly ManualClock testClock = new ManualClock();
private TestDrawableScrollingRuleset drawableRuleset;
[SetUp]
public void Setup() => Schedule(() => testClock.CurrentTime = 0);
[Test]
public void TestRelativeBeatLengthScaleSingleTimingPoint()
{
var beatmap = createBeatmap();
beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = time_range / 2 });
createTest(beatmap, d => d.RelativeScaleBeatLengthsOverride = true);
assertPosition(0, 0f);
// The single timing point is 1x speed relative to itself, such that the hitobject occurring time_range milliseconds later should appear
// at the bottom of the view window regardless of the timing point's beat length
assertPosition(1, 1f);
}
[Test]
public void TestRelativeBeatLengthScaleTimingPointBeyondEndDoesNotBecomeDominant()
{
var beatmap = createBeatmap();
beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = time_range / 2 });
beatmap.ControlPointInfo.Add(12000, new TimingControlPoint { BeatLength = time_range });
beatmap.ControlPointInfo.Add(100000, new TimingControlPoint { BeatLength = time_range });
createTest(beatmap, d => d.RelativeScaleBeatLengthsOverride = true);
assertPosition(0, 0f);
assertPosition(1, 1f);
}
[Test]
public void TestRelativeBeatLengthScaleFromSecondTimingPoint()
{
var beatmap = createBeatmap();
beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = time_range });
beatmap.ControlPointInfo.Add(3 * time_range, new TimingControlPoint { BeatLength = time_range / 2 });
createTest(beatmap, d => d.RelativeScaleBeatLengthsOverride = true);
// The first timing point should have a relative velocity of 2
assertPosition(0, 0f);
assertPosition(1, 0.5f);
assertPosition(2, 1f);
// Move to the second timing point
setTime(3 * time_range);
assertPosition(3, 0f);
// As above, this is the timing point that is 1x speed relative to itself, so the hitobject occurring time_range milliseconds later should be at the bottom of the view window
assertPosition(4, 1f);
}
[Test]
public void TestNonRelativeScale()
{
var beatmap = createBeatmap();
beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = time_range });
beatmap.ControlPointInfo.Add(3 * time_range, new TimingControlPoint { BeatLength = time_range / 2 });
createTest(beatmap);
assertPosition(0, 0f);
assertPosition(1, 1);
// Move to the second timing point
setTime(3 * time_range);
assertPosition(3, 0f);
// For a beat length of 500, the view window of this timing point is elongated 2x (1000 / 500), such that the second hitobject is two TimeRanges away (offscreen)
// To bring it on-screen, half TimeRange is added to the current time, bringing the second half of the view window into view, and the hitobject should appear at the bottom
setTime(3 * time_range + time_range / 2);
assertPosition(4, 1f);
}
[Test]
public void TestSliderMultiplierDoesNotAffectRelativeBeatLength()
{
var beatmap = createBeatmap();
beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = time_range });
beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 2;
createTest(beatmap, d => d.RelativeScaleBeatLengthsOverride = true);
AddStep("adjust time range", () => drawableRuleset.TimeRange.Value = 5000);
for (int i = 0; i < 5; i++)
assertPosition(i, i / 5f);
}
[Test]
public void TestSliderMultiplierAffectsNonRelativeBeatLength()
{
var beatmap = createBeatmap();
beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = time_range });
beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 2;
createTest(beatmap);
AddStep("adjust time range", () => drawableRuleset.TimeRange.Value = 2000);
assertPosition(0, 0);
assertPosition(1, 1);
}
private void assertPosition(int index, float relativeY) => AddAssert($"hitobject {index} at {relativeY}",
() => Precision.AlmostEquals(drawableRuleset.Playfield.AllHitObjects.ElementAt(index).DrawPosition.Y, drawableRuleset.Playfield.HitObjectContainer.DrawHeight * relativeY));
private void setTime(double time)
{
AddStep($"set time = {time}", () => testClock.CurrentTime = time);
}
/// <summary>
/// Creates an <see cref="IBeatmap"/>, containing 10 hitobjects and user-provided timing points.
/// The hitobjects are spaced <see cref="time_range"/> milliseconds apart.
/// </summary>
/// <returns>The <see cref="IBeatmap"/>.</returns>
private IBeatmap createBeatmap()
{
var beatmap = new Beatmap<HitObject> { BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo } };
for (int i = 0; i < 10; i++)
beatmap.HitObjects.Add(new HitObject { StartTime = i * time_range });
return beatmap;
}
private void createTest(IBeatmap beatmap, Action<TestDrawableScrollingRuleset> overrideAction = null) => AddStep("create test", () =>
{
var ruleset = new TestScrollingRuleset();
drawableRuleset = (TestDrawableScrollingRuleset)ruleset.CreateDrawableRulesetWith(CreateWorkingBeatmap(beatmap), Array.Empty<Mod>());
drawableRuleset.FrameStablePlayback = false;
overrideAction?.Invoke(drawableRuleset);
Child = new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Y,
Height = 0.75f,
Width = 400,
Masking = true,
Clock = new FramedClock(testClock),
Child = drawableRuleset
};
});
#region Ruleset
private class TestScrollingRuleset : Ruleset
{
public TestScrollingRuleset(RulesetInfo rulesetInfo = null)
: base(rulesetInfo)
{
}
public override IEnumerable<Mod> GetModsFor(ModType type) => throw new NotImplementedException();
public override DrawableRuleset CreateDrawableRulesetWith(IWorkingBeatmap beatmap, IReadOnlyList<Mod> mods) => new TestDrawableScrollingRuleset(this, beatmap, mods);
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new TestBeatmapConverter(beatmap);
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => throw new NotImplementedException();
public override string Description { get; } = string.Empty;
public override string ShortName { get; } = string.Empty;
}
private class TestDrawableScrollingRuleset : DrawableScrollingRuleset<TestHitObject>
{
public bool RelativeScaleBeatLengthsOverride { get; set; }
protected override bool RelativeScaleBeatLengths => RelativeScaleBeatLengthsOverride;
protected override ScrollVisualisationMethod VisualisationMethod => ScrollVisualisationMethod.Overlapping;
public new Bindable<double> TimeRange => base.TimeRange;
public TestDrawableScrollingRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList<Mod> mods)
: base(ruleset, beatmap, mods)
{
TimeRange.Value = time_range;
}
public override DrawableHitObject<TestHitObject> CreateDrawableRepresentation(TestHitObject h) => new DrawableTestHitObject(h);
protected override PassThroughInputManager CreateInputManager() => new PassThroughInputManager();
protected override Playfield CreatePlayfield() => new TestPlayfield();
}
private class TestPlayfield : ScrollingPlayfield
{
public TestPlayfield()
{
AddInternal(new Container
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0.2f,
},
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Top = 150 },
Children = new Drawable[]
{
new Box
{
Anchor = Anchor.TopCentre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.X,
Height = 2,
Colour = Color4.Green
},
HitObjectContainer
}
}
}
});
}
}
private class TestBeatmapConverter : BeatmapConverter<TestHitObject>
{
public TestBeatmapConverter(IBeatmap beatmap)
: base(beatmap)
{
}
protected override IEnumerable<Type> ValidConversionTypes => new[] { typeof(HitObject) };
protected override IEnumerable<TestHitObject> ConvertHitObject(HitObject original, IBeatmap beatmap)
{
yield return new TestHitObject
{
StartTime = original.StartTime,
EndTime = (original as IHasEndTime)?.EndTime ?? (original.StartTime + 100)
};
}
}
#endregion
#region HitObject
private class TestHitObject : HitObject, IHasEndTime
{
public double EndTime { get; set; }
public double Duration => EndTime - StartTime;
}
private class DrawableTestHitObject : DrawableHitObject<TestHitObject>
{
public DrawableTestHitObject(TestHitObject hitObject)
: base(hitObject)
{
Anchor = Anchor.TopCentre;
Origin = Anchor.TopCentre;
Size = new Vector2(100, 25);
AddRangeInternal(new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.LightPink
},
new Box
{
Origin = Anchor.CentreLeft,
RelativeSizeAxes = Axes.X,
Height = 2,
Colour = Color4.Red
}
});
}
}
#endregion
}
}

View File

@ -0,0 +1,50 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Screens.Play;
namespace osu.Game.Tests.Visual.Gameplay
{
public class TestSceneFailAnimation : AllPlayersTestScene
{
protected override Player CreatePlayer(Ruleset ruleset)
{
Mods.Value = Array.Empty<Mod>();
return new FailPlayer();
}
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(AllPlayersTestScene),
typeof(TestPlayer),
typeof(Player),
};
protected override void AddCheckSteps()
{
AddUntilStep("wait for fail", () => Player.HasFailed);
AddUntilStep("wait for fail overlay", () => ((FailPlayer)Player).FailOverlay.State.Value == Visibility.Visible);
}
private class FailPlayer : TestPlayer
{
public new FailOverlay FailOverlay => base.FailOverlay;
public FailPlayer()
: base(false, false)
{
}
protected override void LoadComplete()
{
base.LoadComplete();
ScoreProcessor.FailConditions += (_, __) => true;
}
}
}
}

View File

@ -0,0 +1,44 @@
// 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 osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Play;
namespace osu.Game.Tests.Visual.Gameplay
{
public class TestSceneFailJudgement : AllPlayersTestScene
{
protected override Player CreatePlayer(Ruleset ruleset)
{
Mods.Value = Array.Empty<Mod>();
return new FailPlayer();
}
protected override void AddCheckSteps()
{
AddUntilStep("wait for fail", () => Player.HasFailed);
AddUntilStep("wait for multiple judged objects", () => ((FailPlayer)Player).DrawableRuleset.Playfield.AllHitObjects.Count(h => h.AllJudged) > 1);
AddAssert("total judgements == 1", () => ((FailPlayer)Player).ScoreProcessor.JudgedHits == 1);
}
private class FailPlayer : TestPlayer
{
public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
public FailPlayer()
: base(false, false)
{
}
protected override void LoadComplete()
{
base.LoadComplete();
ScoreProcessor.FailConditions += (_, __) => true;
}
}
}
}

View File

@ -3,12 +3,13 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Logging;
using osu.Game.Graphics.UserInterface;
using osu.Game.Input.Bindings;
using osu.Game.Screens.Play;
using osuTK;
@ -29,57 +30,118 @@ namespace osu.Game.Tests.Visual.Gameplay
[BackgroundDependencyLoader]
private void load(OsuGameBase game)
{
Child = globalActionContainer = new GlobalActionContainer(game)
{
Children = new Drawable[]
{
pauseOverlay = new PauseOverlay
{
OnResume = () => Logger.Log(@"Resume"),
OnRetry = () => Logger.Log(@"Retry"),
OnQuit = () => Logger.Log(@"Quit"),
},
failOverlay = new FailOverlay
Child = globalActionContainer = new GlobalActionContainer(game);
}
{
OnRetry = () => Logger.Log(@"Retry"),
OnQuit = () => Logger.Log(@"Quit"),
}
[SetUp]
public void SetUp() => Schedule(() =>
{
globalActionContainer.Children = new Drawable[]
{
pauseOverlay = new PauseOverlay
{
OnResume = () => Logger.Log(@"Resume"),
OnRetry = () => Logger.Log(@"Retry"),
OnQuit = () => Logger.Log(@"Quit"),
},
failOverlay = new FailOverlay
{
OnRetry = () => Logger.Log(@"Retry"),
OnQuit = () => Logger.Log(@"Quit"),
}
};
InputManager.MoveMouseTo(Vector2.Zero);
});
[Test]
public void TestAdjustRetryCount()
{
showOverlay();
var retryCount = 0;
AddStep("Add retry", () =>
AddRepeatStep("Add retry", () =>
{
retryCount++;
pauseOverlay.Retries = failOverlay.Retries = retryCount;
});
}, 10);
}
AddToggleStep("Toggle pause overlay", t => pauseOverlay.ToggleVisibility());
AddToggleStep("Toggle fail overlay", t => failOverlay.ToggleVisibility());
/// <summary>
/// Tests that pressing enter after an overlay shows doesn't trigger an event because a selection hasn't occurred.
/// </summary>
[Test]
public void TestEnterWithoutSelection()
{
showOverlay();
testHideResets();
AddStep("Press select", () => press(GlobalAction.Select));
AddAssert("Overlay still open", () => pauseOverlay.State.Value == Visibility.Visible);
}
testEnterWithoutSelection();
testKeyUpFromInitial();
testKeyDownFromInitial();
testKeyUpWrapping();
testKeyDownWrapping();
/// <summary>
/// Tests that pressing the up arrow from the initial state selects the last button.
/// </summary>
[Test]
public void TestKeyUpFromInitial()
{
showOverlay();
testMouseSelectionAfterKeySelection();
testKeySelectionAfterMouseSelection();
AddStep("Up arrow", () => press(Key.Up));
AddAssert("Last button selected", () => pauseOverlay.Buttons.Last().Selected.Value);
}
testMouseDeselectionResets();
/// <summary>
/// Tests that pressing the down arrow from the initial state selects the first button.
/// </summary>
[Test]
public void TestKeyDownFromInitial()
{
showOverlay();
testClickSelection();
testEnterKeySelection();
AddStep("Down arrow", () => press(Key.Down));
AddAssert("First button selected", () => getButton(0).Selected.Value);
}
/// <summary>
/// Tests that pressing the up arrow repeatedly causes the selected button to wrap correctly.
/// </summary>
[Test]
public void TestKeyUpWrapping()
{
AddStep("Show overlay", () => failOverlay.Show());
AddStep("Up arrow", () => press(Key.Up));
AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected.Value);
AddStep("Up arrow", () => press(Key.Up));
AddAssert("First button selected", () => failOverlay.Buttons.First().Selected.Value);
AddStep("Up arrow", () => press(Key.Up));
AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected.Value);
}
/// <summary>
/// Tests that pressing the down arrow repeatedly causes the selected button to wrap correctly.
/// </summary>
[Test]
public void TestKeyDownWrapping()
{
AddStep("Show overlay", () => failOverlay.Show());
AddStep("Down arrow", () => press(Key.Down));
AddAssert("First button selected", () => failOverlay.Buttons.First().Selected.Value);
AddStep("Down arrow", () => press(Key.Down));
AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected.Value);
AddStep("Down arrow", () => press(Key.Down));
AddAssert("First button selected", () => failOverlay.Buttons.First().Selected.Value);
}
/// <summary>
/// Test that hiding the overlay after hovering a button will reset the overlay to the initial state with no buttons selected.
/// </summary>
private void testHideResets()
[Test]
public void TestHideResets()
{
AddStep("Show overlay", () => failOverlay.Show());
@ -90,141 +152,78 @@ namespace osu.Game.Tests.Visual.Gameplay
}
/// <summary>
/// Tests that pressing enter after an overlay shows doesn't trigger an event because a selection hasn't occurred.
/// Tests that entering menu with cursor initially on button doesn't selects it immediately.
/// This is to allow for stable keyboard navigation.
/// </summary>
private void testEnterWithoutSelection()
[Test]
public void TestInitialButtonHover()
{
AddStep("Show overlay", () => pauseOverlay.Show());
showOverlay();
AddStep("Press select", () => press(GlobalAction.Select));
AddAssert("Overlay still open", () => pauseOverlay.State == Visibility.Visible);
AddStep("Hover first button", () => InputManager.MoveMouseTo(getButton(0)));
AddStep("Hide overlay", () => pauseOverlay.Hide());
}
showOverlay();
/// <summary>
/// Tests that pressing the up arrow from the initial state selects the last button.
/// </summary>
private void testKeyUpFromInitial()
{
AddStep("Show overlay", () => pauseOverlay.Show());
AddAssert("First button not selected", () => !getButton(0).Selected.Value);
AddStep("Up arrow", () => press(Key.Up));
AddAssert("Last button selected", () => pauseOverlay.Buttons.Last().Selected.Value);
AddStep("Move slightly", () => InputManager.MoveMouseTo(InputManager.CurrentState.Mouse.Position + new Vector2(1)));
AddStep("Hide overlay", () => pauseOverlay.Hide());
}
/// <summary>
/// Tests that pressing the down arrow from the initial state selects the first button.
/// </summary>
private void testKeyDownFromInitial()
{
AddStep("Show overlay", () => pauseOverlay.Show());
AddStep("Down arrow", () => press(Key.Down));
AddAssert("First button selected", () => pauseOverlay.Buttons.First().Selected.Value);
AddStep("Hide overlay", () => pauseOverlay.Hide());
}
/// <summary>
/// Tests that pressing the up arrow repeatedly causes the selected button to wrap correctly.
/// </summary>
private void testKeyUpWrapping()
{
AddStep("Show overlay", () => failOverlay.Show());
AddStep("Up arrow", () => press(Key.Up));
AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected.Value);
AddStep("Up arrow", () => press(Key.Up));
AddAssert("First button selected", () => failOverlay.Buttons.First().Selected.Value);
AddStep("Up arrow", () => press(Key.Up));
AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected.Value);
AddStep("Hide overlay", () => failOverlay.Hide());
}
/// <summary>
/// Tests that pressing the down arrow repeatedly causes the selected button to wrap correctly.
/// </summary>
private void testKeyDownWrapping()
{
AddStep("Show overlay", () => failOverlay.Show());
AddStep("Down arrow", () => press(Key.Down));
AddAssert("First button selected", () => failOverlay.Buttons.First().Selected.Value);
AddStep("Down arrow", () => press(Key.Down));
AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected.Value);
AddStep("Down arrow", () => press(Key.Down));
AddAssert("First button selected", () => failOverlay.Buttons.First().Selected.Value);
AddStep("Hide overlay", () => failOverlay.Hide());
AddAssert("First button selected", () => getButton(0).Selected.Value);
}
/// <summary>
/// Tests that hovering a button that was previously selected with the keyboard correctly selects the new button and deselects the previous button.
/// </summary>
private void testMouseSelectionAfterKeySelection()
[Test]
public void TestMouseSelectionAfterKeySelection()
{
AddStep("Show overlay", () => pauseOverlay.Show());
var secondButton = pauseOverlay.Buttons.Skip(1).First();
showOverlay();
AddStep("Down arrow", () => press(Key.Down));
AddStep("Hover second button", () => InputManager.MoveMouseTo(secondButton));
AddAssert("First button not selected", () => !pauseOverlay.Buttons.First().Selected.Value);
AddAssert("Second button selected", () => secondButton.Selected.Value);
AddStep("Hide overlay", () => pauseOverlay.Hide());
AddStep("Hover second button", () => InputManager.MoveMouseTo(getButton(1)));
AddAssert("First button not selected", () => !getButton(0).Selected.Value);
AddAssert("Second button selected", () => getButton(1).Selected.Value);
}
/// <summary>
/// Tests that pressing a key after selecting a button with a hover event correctly selects a new button and deselects the previous button.
/// </summary>
private void testKeySelectionAfterMouseSelection()
[Test]
public void TestKeySelectionAfterMouseSelection()
{
AddStep("Show overlay", () =>
{
pauseOverlay.Show();
InputManager.MoveMouseTo(Vector2.Zero);
});
var secondButton = pauseOverlay.Buttons.Skip(1).First();
AddStep("Hover second button", () => InputManager.MoveMouseTo(secondButton));
AddStep("Hover second button", () => InputManager.MoveMouseTo(getButton(1)));
AddStep("Up arrow", () => press(Key.Up));
AddAssert("Second button not selected", () => !secondButton.Selected.Value);
AddAssert("First button selected", () => pauseOverlay.Buttons.First().Selected.Value);
AddStep("Hide overlay", () => pauseOverlay.Hide());
AddAssert("Second button not selected", () => !getButton(1).Selected.Value);
AddAssert("First button selected", () => getButton(0).Selected.Value);
}
/// <summary>
/// Tests that deselecting with the mouse by losing hover will reset the overlay to the initial state.
/// </summary>
private void testMouseDeselectionResets()
[Test]
public void TestMouseDeselectionResets()
{
AddStep("Show overlay", () => pauseOverlay.Show());
showOverlay();
var secondButton = pauseOverlay.Buttons.Skip(1).First();
AddStep("Hover second button", () => InputManager.MoveMouseTo(secondButton));
AddStep("Hover second button", () => InputManager.MoveMouseTo(getButton(1)));
AddStep("Unhover second button", () => InputManager.MoveMouseTo(Vector2.Zero));
AddStep("Down arrow", () => press(Key.Down));
AddAssert("First button selected", () => pauseOverlay.Buttons.First().Selected.Value); // Initial state condition
AddStep("Hide overlay", () => pauseOverlay.Hide());
AddAssert("First button selected", () => getButton(0).Selected.Value); // Initial state condition
}
/// <summary>
/// Tests that clicking on a button correctly causes a click event for that button.
/// </summary>
private void testClickSelection()
[Test]
public void TestClickSelection()
{
AddStep("Show overlay", () => pauseOverlay.Show());
var retryButton = pauseOverlay.Buttons.Skip(1).First();
showOverlay();
bool triggered = false;
AddStep("Click retry button", () =>
@ -232,20 +231,21 @@ namespace osu.Game.Tests.Visual.Gameplay
var lastAction = pauseOverlay.OnRetry;
pauseOverlay.OnRetry = () => triggered = true;
retryButton.Click();
getButton(1).Click();
pauseOverlay.OnRetry = lastAction;
});
AddAssert("Action was triggered", () => triggered);
AddAssert("Overlay is closed", () => pauseOverlay.State == Visibility.Hidden);
AddAssert("Overlay is closed", () => pauseOverlay.State.Value == Visibility.Hidden);
}
/// <summary>
/// Tests that pressing the enter key with a button selected correctly causes a click event for that button.
/// </summary>
private void testEnterKeySelection()
[Test]
public void TestEnterKeySelection()
{
AddStep("Show overlay", () => pauseOverlay.Show());
showOverlay();
AddStep("Select second button", () =>
{
@ -272,9 +272,13 @@ namespace osu.Game.Tests.Visual.Gameplay
return triggered;
});
AddAssert("Overlay is closed", () => pauseOverlay.State == Visibility.Hidden);
AddAssert("Overlay is closed", () => pauseOverlay.State.Value == Visibility.Hidden);
}
private void showOverlay() => AddStep("Show overlay", () => pauseOverlay.Show());
private DialogButton getButton(int index) => pauseOverlay.Buttons.Skip(index).First();
private void press(Key key)
{
InputManager.PressKey(key);

View File

@ -0,0 +1,116 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Track;
using osu.Framework.MathUtils;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.Play;
using osuTK;
namespace osu.Game.Tests.Visual.Gameplay
{
public class TestSceneGameplayRewinding : PlayerTestScene
{
private RulesetExposingPlayer player => (RulesetExposingPlayer)Player;
[Resolved]
private AudioManager audioManager { get; set; }
public TestSceneGameplayRewinding()
: base(new OsuRuleset())
{
}
private Track track;
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap)
{
var working = new ClockBackedTestWorkingBeatmap(beatmap, new FramedClock(new ManualClock { Rate = 1 }), audioManager);
track = working.Track;
return working;
}
[Test]
public void TestNoJudgementsOnRewind()
{
AddUntilStep("wait for track to start running", () => track.IsRunning);
addSeekStep(3000);
AddAssert("all judged", () => player.DrawableRuleset.Playfield.AllHitObjects.All(h => h.Judged));
AddUntilStep("key counter counted keys", () => player.HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses >= 7));
AddStep("clear results", () => player.AppliedResults.Clear());
addSeekStep(0);
AddAssert("none judged", () => player.DrawableRuleset.Playfield.AllHitObjects.All(h => !h.Judged));
AddUntilStep("key counters reset", () => player.HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses == 0));
AddAssert("no results triggered", () => player.AppliedResults.Count == 0);
}
private void addSeekStep(double time)
{
AddStep($"seek to {time}", () => track.Seek(time));
// Allow a few frames of lenience
AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time, player.DrawableRuleset.FrameStableClock.CurrentTime, 100));
}
protected override Player CreatePlayer(Ruleset ruleset)
{
Mods.Value = Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() }).ToArray();
return new RulesetExposingPlayer();
}
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
{
var beatmap = new Beatmap
{
BeatmapInfo = { BaseDifficulty = { ApproachRate = 9 } },
};
for (int i = 0; i < 15; i++)
{
beatmap.HitObjects.Add(new HitCircle
{
Position = new Vector2(256, 192),
StartTime = 1000 + 30 * i
});
}
return beatmap;
}
private class RulesetExposingPlayer : Player
{
public readonly List<JudgementResult> AppliedResults = new List<JudgementResult>();
public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
public new HUDOverlay HUDOverlay => base.HUDOverlay;
public new GameplayClockContainer GameplayClockContainer => base.GameplayClockContainer;
public new DrawableRuleset DrawableRuleset => base.DrawableRuleset;
public RulesetExposingPlayer()
: base(false, false)
{
}
[BackgroundDependencyLoader]
private void load()
{
ScoreProcessor.NewJudgement += r => AppliedResults.Add(r);
}
}
}
}

View File

@ -17,6 +17,8 @@ namespace osu.Game.Tests.Visual.Gameplay
{
private bool exitAction;
protected override double TimePerAction => 100; // required for the early exit test, since hold-to-confirm delay is 200ms
[BackgroundDependencyLoader]
private void load()
{

View File

@ -7,7 +7,6 @@ using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.MathUtils;
using osu.Framework.Timing;
using osu.Game.Screens.Play;
using osuTK.Input;
@ -25,14 +24,15 @@ namespace osu.Game.Tests.Visual.Gameplay
public TestSceneKeyCounter()
{
KeyCounterKeyboard rewindTestKeyCounterKeyboard;
KeyCounterKeyboard testCounter;
KeyCounterDisplay kc = new KeyCounterDisplay
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Children = new KeyCounter[]
{
rewindTestKeyCounterKeyboard = new KeyCounterKeyboard(Key.X),
testCounter = new KeyCounterKeyboard(Key.X),
new KeyCounterKeyboard(Key.X),
new KeyCounterMouse(MouseButton.Left),
new KeyCounterMouse(MouseButton.Right),
@ -44,10 +44,8 @@ namespace osu.Game.Tests.Visual.Gameplay
Key key = (Key)((int)Key.A + RNG.Next(26));
kc.Add(new KeyCounterKeyboard(key));
});
AddSliderStep("Fade time", 0, 200, 50, v => kc.FadeTime = v);
Key testKey = ((KeyCounterKeyboard)kc.Children.First()).Key;
double time1 = 0;
AddStep($"Press {testKey} key", () =>
{
@ -55,48 +53,17 @@ namespace osu.Game.Tests.Visual.Gameplay
InputManager.ReleaseKey(testKey);
});
AddAssert($"Check {testKey} counter after keypress", () => rewindTestKeyCounterKeyboard.CountPresses == 1);
AddAssert($"Check {testKey} counter after keypress", () => testCounter.CountPresses == 1);
AddStep($"Press {testKey} key", () =>
{
InputManager.PressKey(testKey);
InputManager.ReleaseKey(testKey);
time1 = Clock.CurrentTime;
});
AddAssert($"Check {testKey} counter after keypress", () => rewindTestKeyCounterKeyboard.CountPresses == 2);
IFrameBasedClock oldClock = null;
AddStep($"Rewind {testKey} counter once", () =>
{
oldClock = rewindTestKeyCounterKeyboard.Clock;
rewindTestKeyCounterKeyboard.Clock = new FramedOffsetClock(new FixedClock(time1 - 10));
});
AddAssert($"Check {testKey} counter after rewind", () => rewindTestKeyCounterKeyboard.CountPresses == 1);
AddStep($"Rewind {testKey} counter to zero", () => rewindTestKeyCounterKeyboard.Clock = new FramedOffsetClock(new FixedClock(0)));
AddAssert($"Check {testKey} counter after rewind", () => rewindTestKeyCounterKeyboard.CountPresses == 0);
AddStep("Restore clock", () => rewindTestKeyCounterKeyboard.Clock = oldClock);
AddAssert($"Check {testKey} counter after keypress", () => testCounter.CountPresses == 2);
Add(kc);
}
private class FixedClock : IClock
{
private readonly double time;
public FixedClock(double time)
{
this.time = time;
}
public double CurrentTime => time;
public double Rate => 1;
public bool IsRunning => false;
}
}
}

View File

@ -69,6 +69,24 @@ namespace osu.Game.Tests.Visual.Gameplay
confirmClockRunning(true);
}
[Test]
public void TestPauseWithResumeOverlay()
{
AddStep("move cursor to center", () => InputManager.MoveMouseTo(Player.ScreenSpaceDrawQuad.Centre));
AddUntilStep("wait for hitobjects", () => Player.ScoreProcessor.Health.Value < 1);
pauseAndConfirm();
resume();
confirmClockRunning(false);
confirmPauseOverlayShown(false);
pauseAndConfirm();
AddUntilStep("resume overlay is not active", () => Player.DrawableRuleset.ResumeOverlay.State.Value == Visibility.Hidden);
confirmPaused();
}
[Test]
public void TestResumeWithResumeOverlaySkipped()
{
@ -113,7 +131,7 @@ namespace osu.Game.Tests.Visual.Gameplay
public void TestPauseAfterFail()
{
AddUntilStep("wait for fail", () => Player.HasFailed);
AddAssert("fail overlay shown", () => Player.FailOverlayVisible);
AddUntilStep("fail overlay shown", () => Player.FailOverlayVisible);
confirmClockRunning(false);
@ -127,13 +145,62 @@ namespace osu.Game.Tests.Visual.Gameplay
exitAndConfirm();
}
[Test]
public void TestExitFromFailedGameplay()
{
AddUntilStep("wait for fail", () => Player.HasFailed);
AddStep("exit", () => Player.Exit());
confirmExited();
}
[Test]
public void TestQuickRetryFromFailedGameplay()
{
AddUntilStep("wait for fail", () => Player.HasFailed);
AddStep("quick retry", () => Player.GameplayClockContainer.OfType<HotkeyRetryOverlay>().First().Action?.Invoke());
confirmExited();
}
[Test]
public void TestQuickExitFromFailedGameplay()
{
AddUntilStep("wait for fail", () => Player.HasFailed);
AddStep("quick exit", () => Player.GameplayClockContainer.OfType<HotkeyExitOverlay>().First().Action?.Invoke());
confirmExited();
}
[Test]
public void TestExitFromGameplay()
{
AddStep("exit", () => Player.Exit());
confirmExited();
}
[Test]
public void TestQuickExitFromGameplay()
{
AddStep("quick exit", () => Player.GameplayClockContainer.OfType<HotkeyExitOverlay>().First().Action?.Invoke());
confirmExited();
}
[Test]
public void TestExitViaHoldToExit()
{
AddStep("exit", () =>
{
InputManager.MoveMouseTo(Player.HUDOverlay.HoldToQuit.First(c => c is HoldToConfirmContainer));
InputManager.PressButton(MouseButton.Left);
});
confirmPaused();
AddStep("release", () => InputManager.ReleaseButton(MouseButton.Left));
exitAndConfirm();
}
@ -144,6 +211,15 @@ namespace osu.Game.Tests.Visual.Gameplay
exitAndConfirm();
}
[Test]
public void TestRestartAfterResume()
{
pauseAndConfirm();
resumeAndConfirm();
restart();
confirmExited();
}
private void pauseAndConfirm()
{
pause();
@ -161,6 +237,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("player not exited", () => Player.IsCurrentScreen());
AddStep("exit", () => Player.Exit());
confirmExited();
confirmNoTrackAdjustments();
}
private void confirmPaused()
@ -182,6 +259,12 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("player exited", () => !Player.IsCurrentScreen());
}
private void confirmNoTrackAdjustments()
{
AddAssert("track has no adjustments", () => Beatmap.Value.Track.AggregateFrequency.Value == 1);
}
private void restart() => AddStep("restart", () => Player.Restart());
private void pause() => AddStep("pause", () => Player.Pause());
private void resume() => AddStep("resume", () => Player.Resume());
@ -189,7 +272,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddAssert("pause overlay " + (isShown ? "shown" : "hidden"), () => Player.PauseOverlayVisible == isShown);
private void confirmClockRunning(bool isRunning) =>
AddAssert("clock " + (isRunning ? "running" : "stopped"), () => Player.GameplayClockContainer.GameplayClock.IsRunning == isRunning);
AddUntilStep("clock " + (isRunning ? "running" : "stopped"), () => Player.GameplayClockContainer.GameplayClock.IsRunning == isRunning);
protected override bool AllowFail => true;
@ -203,9 +286,9 @@ namespace osu.Game.Tests.Visual.Gameplay
public new HUDOverlay HUDOverlay => base.HUDOverlay;
public bool FailOverlayVisible => FailOverlay.State == Visibility.Visible;
public bool FailOverlayVisible => FailOverlay.State.Value == Visibility.Visible;
public bool PauseOverlayVisible => PauseOverlay.State == Visibility.Visible;
public bool PauseOverlayVisible => PauseOverlay.State.Value == Visibility.Visible;
public override void OnEntering(IScreen last)
{

View File

@ -1,4 +1,4 @@
// 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 System;
@ -7,44 +7,86 @@ using System.Linq;
using System.Threading;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.MathUtils;
using osu.Framework.Screens;
using osu.Game.Configuration;
using osu.Game.Graphics.Containers;
using osu.Game.Overlays;
using osu.Game.Overlays.Notifications;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using osu.Game.Screens;
using osu.Game.Screens.Play;
using osu.Game.Tests.Beatmaps;
using osu.Game.Screens.Play.PlayerSettings;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Gameplay
{
public class TestScenePlayerLoader : ManualInputManagerTestScene
{
private PlayerLoader loader;
private OsuScreenStack stack;
private TestPlayerLoader loader;
private TestPlayerLoaderContainer container;
private TestPlayer player;
[SetUp]
public void Setup() => Schedule(() =>
[Resolved]
private AudioManager audioManager { get; set; }
[Resolved]
private SessionStatics sessionStatics { get; set; }
/// <summary>
/// Sets the input manager child to a new test player loader container instance.
/// </summary>
/// <param name="interactive">If the test player should behave like the production one.</param>
/// <param name="beforeLoadAction">An action to run before player load but after bindable leases are returned.</param>
/// <param name="afterLoadAction">An action to run after container load.</param>
public void ResetPlayer(bool interactive, Action beforeLoadAction = null, Action afterLoadAction = null)
{
InputManager.Child = stack = new OsuScreenStack { RelativeSizeAxes = Axes.Both };
Beatmap.Value = new TestWorkingBeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo), Clock);
});
audioManager.Volume.SetDefault();
InputManager.Clear();
beforeLoadAction?.Invoke();
Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
InputManager.Child = container = new TestPlayerLoaderContainer(
loader = new TestPlayerLoader(() =>
{
afterLoadAction?.Invoke();
return player = new TestPlayer(interactive, interactive);
}));
}
[Test]
public void TestBlockLoadViaMouseMovement()
{
AddStep("load dummy beatmap", () => ResetPlayer(false));
AddUntilStep("wait for current", () => loader.IsCurrentScreen());
AddRepeatStep("move mouse", () => InputManager.MoveMouseTo(loader.VisualSettings.ScreenSpaceDrawQuad.TopLeft + (loader.VisualSettings.ScreenSpaceDrawQuad.BottomRight - loader.VisualSettings.ScreenSpaceDrawQuad.TopLeft) * RNG.NextSingle()), 20);
AddAssert("loader still active", () => loader.IsCurrentScreen());
AddUntilStep("loads after idle", () => !loader.IsCurrentScreen());
}
[Test]
public void TestLoadContinuation()
{
Player player = null;
SlowLoadPlayer slowPlayer = null;
AddStep("load dummy beatmap", () => stack.Push(loader = new PlayerLoader(() => player = new TestPlayer(false, false))));
AddStep("load dummy beatmap", () => ResetPlayer(false));
AddUntilStep("wait for current", () => loader.IsCurrentScreen());
AddStep("mouse in centre", () => InputManager.MoveMouseTo(loader.ScreenSpaceDrawQuad.Centre));
AddUntilStep("wait for player to be current", () => player.IsCurrentScreen());
AddStep("load slow dummy beatmap", () =>
{
stack.Push(loader = new PlayerLoader(() => slowPlayer = new SlowLoadPlayer(false, false)));
InputManager.Child = container = new TestPlayerLoaderContainer(
loader = new TestPlayerLoader(() => slowPlayer = new SlowLoadPlayer(false, false)));
Scheduler.AddDelayed(() => slowPlayer.AllowLoad.Set(), 5000);
});
@ -54,16 +96,11 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestModReinstantiation()
{
TestPlayer player = null;
TestMod gameMod = null;
TestMod playerMod1 = null;
TestMod playerMod2 = null;
AddStep("load player", () =>
{
Mods.Value = new[] { gameMod = new TestMod() };
stack.Push(loader = new PlayerLoader(() => player = new TestPlayer()));
});
AddStep("load player", () => { ResetPlayer(true, () => Mods.Value = new[] { gameMod = new TestMod() }); });
AddUntilStep("wait for loader to become current", () => loader.IsCurrentScreen());
AddStep("mouse in centre", () => InputManager.MoveMouseTo(loader.ScreenSpaceDrawQuad.Centre));
@ -86,6 +123,85 @@ namespace osu.Game.Tests.Visual.Gameplay
AddAssert("player mods applied", () => playerMod2.Applied);
}
[Test]
public void TestMutedNotificationMasterVolume() => addVolumeSteps("master volume", () => audioManager.Volume.Value = 0, null, () => audioManager.Volume.IsDefault);
[Test]
public void TestMutedNotificationTrackVolume() => addVolumeSteps("music volume", () => audioManager.VolumeTrack.Value = 0, null, () => audioManager.VolumeTrack.IsDefault);
[Test]
public void TestMutedNotificationMuteButton() => addVolumeSteps("mute button", null, () => container.VolumeOverlay.IsMuted.Value = true, () => !container.VolumeOverlay.IsMuted.Value);
/// <remarks>
/// Created for avoiding copy pasting code for the same steps.
/// </remarks>
/// <param name="volumeName">What part of the volume system is checked</param>
/// <param name="beforeLoad">The action to be invoked to set the volume before loading</param>
/// <param name="afterLoad">The action to be invoked to set the volume after loading</param>
/// <param name="assert">The function to be invoked and checked</param>
private void addVolumeSteps(string volumeName, Action beforeLoad, Action afterLoad, Func<bool> assert)
{
AddStep("reset notification lock", () => sessionStatics.GetBindable<bool>(Static.MutedAudioNotificationShownOnce).Value = false);
AddStep("load player", () => ResetPlayer(false, beforeLoad, afterLoad));
AddUntilStep("wait for player", () => player.IsLoaded);
AddAssert("check for notification", () => container.NotificationOverlay.UnreadCount.Value == 1);
AddStep("click notification", () =>
{
var scrollContainer = (OsuScrollContainer)container.NotificationOverlay.Children.Last();
var flowContainer = scrollContainer.Children.OfType<FillFlowContainer<NotificationSection>>().First();
var notification = flowContainer.First();
InputManager.MoveMouseTo(notification);
InputManager.Click(MouseButton.Left);
});
AddAssert("check " + volumeName, assert);
}
private class TestPlayerLoaderContainer : Container
{
[Cached]
public readonly NotificationOverlay NotificationOverlay;
[Cached]
public readonly VolumeOverlay VolumeOverlay;
public TestPlayerLoaderContainer(IScreen screen)
{
RelativeSizeAxes = Axes.Both;
InternalChildren = new Drawable[]
{
new OsuScreenStack(screen)
{
RelativeSizeAxes = Axes.Both,
},
NotificationOverlay = new NotificationOverlay
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
},
VolumeOverlay = new VolumeOverlay
{
Anchor = Anchor.TopLeft,
Origin = Anchor.TopLeft,
}
};
}
}
private class TestPlayerLoader : PlayerLoader
{
public new VisualSettings VisualSettings => base.VisualSettings;
public TestPlayerLoader(Func<Player> createPlayer)
: base(createPlayer)
{
}
}
private class TestMod : Mod, IApplicableToScoreProcessor
{
public override string Name => string.Empty;

View File

@ -3,7 +3,6 @@
using System;
using osu.Framework.Lists;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
using osu.Game.Screens.Play;
@ -43,9 +42,9 @@ namespace osu.Game.Tests.Visual.Gameplay
});
}
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, IFrameBasedClock clock)
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap)
{
var working = base.CreateWorkingBeatmap(beatmap, clock);
var working = base.CreateWorkingBeatmap(beatmap);
workingWeakReferences.Add(working);
return working;
}

View File

@ -26,12 +26,14 @@ namespace osu.Game.Tests.Visual.Gameplay
{
AddUntilStep("score above zero", () => ((ScoreAccessibleReplayPlayer)Player).ScoreProcessor.TotalScore.Value > 0);
AddUntilStep("key counter counted keys", () => ((ScoreAccessibleReplayPlayer)Player).HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 0));
AddAssert("cannot fail", () => !((ScoreAccessibleReplayPlayer)Player).AllowFail);
}
private class ScoreAccessibleReplayPlayer : ReplayPlayer
{
public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
public new HUDOverlay HUDOverlay => base.HUDOverlay;
public new bool AllowFail => base.AllowFail;
protected override bool PauseOnFocusLost => false;

View File

@ -0,0 +1,74 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Game.Online;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Rulesets.Osu;
using osu.Game.Scoring;
using osu.Game.Users;
using System;
using System.Collections.Generic;
using osu.Game.Screens.Ranking.Pages;
namespace osu.Game.Tests.Visual.Gameplay
{
[TestFixture]
public class TestSceneReplayDownloadButton : OsuTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(ReplayDownloadButton)
};
private TestReplayDownloadButton downloadButton;
public TestSceneReplayDownloadButton()
{
createButton(true);
AddStep(@"downloading state", () => downloadButton.SetDownloadState(DownloadState.Downloading));
AddStep(@"locally available state", () => downloadButton.SetDownloadState(DownloadState.LocallyAvailable));
AddStep(@"not downloaded state", () => downloadButton.SetDownloadState(DownloadState.NotDownloaded));
createButton(false);
}
private void createButton(bool withReplay)
{
AddStep(withReplay ? @"create button with replay" : "create button without replay", () =>
{
Child = downloadButton = new TestReplayDownloadButton(getScoreInfo(withReplay))
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
};
});
}
private ScoreInfo getScoreInfo(bool replayAvailable)
{
return new APILegacyScoreInfo
{
ID = 1,
OnlineScoreID = 2553163309,
Ruleset = new OsuRuleset().RulesetInfo,
Replay = replayAvailable,
User = new User
{
Id = 39828,
Username = @"WubWoofWolf",
}
};
}
private class TestReplayDownloadButton : ReplayDownloadButton
{
public void SetDownloadState(DownloadState state) => State.Value = state;
public TestReplayDownloadButton(ScoreInfo score)
: base(score)
{
}
}
}
}

View File

@ -3,6 +3,7 @@
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
using osu.Game.Screens.Play.HUD;
using osu.Game.Screens.Play.PlayerSettings;
@ -20,6 +21,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
State = { Value = Visibility.Visible }
});
Add(container = new ExampleContainer());

View File

@ -3,11 +3,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Screens;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using osu.Game.Screens;
using osu.Game.Screens.Play;
using osu.Game.Screens.Ranking;
using osu.Game.Screens.Ranking.Pages;
@ -22,11 +27,13 @@ namespace osu.Game.Tests.Visual.Gameplay
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(ScoreInfo),
typeof(Results),
typeof(ResultsPage),
typeof(ScoreResultsPage),
typeof(LocalLeaderboardPage)
typeof(RetryButton),
typeof(ReplayDownloadButton),
typeof(LocalLeaderboardPage),
typeof(TestPlayer)
};
[BackgroundDependencyLoader]
@ -42,26 +49,82 @@ namespace osu.Game.Tests.Visual.Gameplay
var beatmapInfo = beatmaps.QueryBeatmap(b => b.RulesetID == 0);
if (beatmapInfo != null)
Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmapInfo);
}
LoadScreen(new SoloResults(new ScoreInfo
private TestSoloResults createResultsScreen() => new TestSoloResults(new ScoreInfo
{
TotalScore = 2845370,
Accuracy = 0.98,
MaxCombo = 123,
Rank = ScoreRank.A,
Date = DateTimeOffset.Now,
Statistics = new Dictionary<HitResult, int>
{
TotalScore = 2845370,
Accuracy = 0.98,
MaxCombo = 123,
Rank = ScoreRank.A,
Date = DateTimeOffset.Now,
Statistics = new Dictionary<HitResult, int>
{ HitResult.Great, 50 },
{ HitResult.Good, 20 },
{ HitResult.Meh, 50 },
{ HitResult.Miss, 1 }
},
User = new User
{
Username = "peppy",
}
});
[Test]
public void ResultsWithoutPlayer()
{
TestSoloResults screen = null;
AddStep("load results", () => Child = new OsuScreenStack(screen = createResultsScreen())
{
RelativeSizeAxes = Axes.Both
});
AddUntilStep("wait for loaded", () => screen.IsLoaded);
AddAssert("retry overlay not present", () => screen.RetryOverlay == null);
}
[Test]
public void ResultsWithPlayer()
{
TestSoloResults screen = null;
AddStep("load results", () => Child = new TestResultsContainer(screen = createResultsScreen()));
AddUntilStep("wait for loaded", () => screen.IsLoaded);
AddAssert("retry overlay present", () => screen.RetryOverlay != null);
}
private class TestResultsContainer : Container
{
[Cached(typeof(Player))]
private readonly Player player = new TestPlayer();
public TestResultsContainer(IScreen screen)
{
RelativeSizeAxes = Axes.Both;
InternalChild = new OsuScreenStack(screen)
{
{ HitResult.Great, 50 },
{ HitResult.Good, 20 },
{ HitResult.Meh, 50 },
{ HitResult.Miss, 1 }
},
User = new User
{
Username = "peppy",
}
}));
RelativeSizeAxes = Axes.Both,
};
}
}
private class TestSoloResults : SoloResults
{
public HotkeyRetryOverlay RetryOverlay;
public TestSoloResults(ScoreInfo score)
: base(score)
{
}
protected override void LoadComplete()
{
base.LoadComplete();
RetryOverlay = InternalChildren.OfType<HotkeyRetryOverlay>().SingleOrDefault();
}
}
}
}

View File

@ -200,10 +200,6 @@ namespace osu.Game.Tests.Visual.Gameplay
break;
}
}
protected override void UpdateState(ArmedState state)
{
}
}
private class TestDrawableHitObject : DrawableHitObject<HitObject>
@ -216,10 +212,6 @@ namespace osu.Game.Tests.Visual.Gameplay
AddInternal(new Box { Size = new Vector2(75) });
}
protected override void UpdateState(ArmedState state)
{
}
}
}
}

View File

@ -1,145 +0,0 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using NUnit.Framework;
using osu.Framework.Audio.Sample;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Textures;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Skinning;
using osuTK.Graphics;
namespace osu.Game.Tests.Visual.Gameplay
{
public class TestSceneSkinReloadable : OsuTestScene
{
[Test]
public void TestInitialLoad()
{
var secondarySource = new SecondarySource();
SkinConsumer consumer = null;
AddStep("setup layout", () =>
{
Child = new SkinSourceContainer
{
RelativeSizeAxes = Axes.Both,
Child = new LocalSkinOverrideContainer(secondarySource)
{
RelativeSizeAxes = Axes.Both,
Child = consumer = new SkinConsumer("test", name => new NamedBox("Default Implementation"), source => true)
}
};
});
AddAssert("consumer using override source", () => consumer.Drawable is SecondarySourceBox);
AddAssert("skinchanged only called once", () => consumer.SkinChangedCount == 1);
}
[Test]
public void TestOverride()
{
var secondarySource = new SecondarySource();
SkinConsumer consumer = null;
Container target = null;
AddStep("setup layout", () =>
{
Child = new SkinSourceContainer
{
RelativeSizeAxes = Axes.Both,
Child = target = new LocalSkinOverrideContainer(secondarySource)
{
RelativeSizeAxes = Axes.Both,
}
};
});
AddStep("add permissive", () => target.Add(consumer = new SkinConsumer("test", name => new NamedBox("Default Implementation"), source => true)));
AddAssert("consumer using override source", () => consumer.Drawable is SecondarySourceBox);
AddAssert("skinchanged only called once", () => consumer.SkinChangedCount == 1);
}
private class NamedBox : Container
{
public NamedBox(string name)
{
Children = new Drawable[]
{
new Box
{
Colour = Color4.Black,
RelativeSizeAxes = Axes.Both,
},
new OsuSpriteText
{
Font = OsuFont.Default.With(size: 40),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Text = name
}
};
}
}
private class SkinConsumer : SkinnableDrawable
{
public new Drawable Drawable => base.Drawable;
public int SkinChangedCount { get; private set; }
public SkinConsumer(string name, Func<string, Drawable> defaultImplementation, Func<ISkinSource, bool> allowFallback = null, bool restrictSize = true)
: base(name, defaultImplementation, allowFallback, restrictSize)
{
}
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
{
base.SkinChanged(skin, allowFallback);
SkinChangedCount++;
}
}
private class BaseSourceBox : NamedBox
{
public BaseSourceBox()
: base("Base Source")
{
}
}
private class SecondarySourceBox : NamedBox
{
public SecondarySourceBox()
: base("Secondary Source")
{
}
}
private class SecondarySource : ISkin
{
public Drawable GetDrawableComponent(string componentName) => new SecondarySourceBox();
public Texture GetTexture(string componentName) => throw new NotImplementedException();
public SampleChannel GetSample(string sampleName) => throw new NotImplementedException();
public TValue GetValue<TConfiguration, TValue>(Func<TConfiguration, TValue> query) where TConfiguration : SkinConfiguration => throw new NotImplementedException();
}
private class SkinSourceContainer : Container, ISkin
{
public Drawable GetDrawableComponent(string componentName) => new BaseSourceBox();
public Texture GetTexture(string componentName) => throw new NotImplementedException();
public SampleChannel GetSample(string sampleName) => throw new NotImplementedException();
public TValue GetValue<TConfiguration, TValue>(Func<TConfiguration, TValue> query) where TConfiguration : SkinConfiguration => throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,350 @@
// 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.Globalization;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Textures;
using osu.Game.Audio;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Skinning;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Tests.Visual.Gameplay
{
public class TestSceneSkinnableDrawable : OsuTestScene
{
[Test]
public void TestConfineScaleDown()
{
FillFlowContainer<ExposedSkinnableDrawable> fill = null;
AddStep("setup layout larger source", () =>
{
Child = new SkinProvidingContainer(new SizedSource(50))
{
RelativeSizeAxes = Axes.Both,
Child = fill = new FillFlowContainer<ExposedSkinnableDrawable>
{
Size = new Vector2(30),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Spacing = new Vector2(10),
Children = new[]
{
new ExposedSkinnableDrawable("default", _ => new DefaultBox(), _ => true),
new ExposedSkinnableDrawable("available", _ => new DefaultBox(), _ => true),
new ExposedSkinnableDrawable("available", _ => new DefaultBox(), _ => true, ConfineMode.ScaleToFit),
new ExposedSkinnableDrawable("available", _ => new DefaultBox(), _ => true, ConfineMode.NoScaling)
}
},
};
});
AddAssert("check sizes", () => fill.Children.Select(c => c.Drawable.DrawWidth).SequenceEqual(new float[] { 30, 30, 30, 50 }));
AddStep("adjust scale", () => fill.Scale = new Vector2(2));
AddAssert("check sizes unchanged by scale", () => fill.Children.Select(c => c.Drawable.DrawWidth).SequenceEqual(new float[] { 30, 30, 30, 50 }));
}
[Test]
public void TestConfineScaleUp()
{
FillFlowContainer<ExposedSkinnableDrawable> fill = null;
AddStep("setup layout larger source", () =>
{
Child = new SkinProvidingContainer(new SizedSource(30))
{
RelativeSizeAxes = Axes.Both,
Child = fill = new FillFlowContainer<ExposedSkinnableDrawable>
{
Size = new Vector2(50),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Spacing = new Vector2(10),
Children = new[]
{
new ExposedSkinnableDrawable("default", _ => new DefaultBox(), _ => true),
new ExposedSkinnableDrawable("available", _ => new DefaultBox(), _ => true),
new ExposedSkinnableDrawable("available", _ => new DefaultBox(), _ => true, ConfineMode.ScaleToFit),
new ExposedSkinnableDrawable("available", _ => new DefaultBox(), _ => true, ConfineMode.NoScaling)
}
},
};
});
AddAssert("check sizes", () => fill.Children.Select(c => c.Drawable.DrawWidth).SequenceEqual(new float[] { 50, 30, 50, 30 }));
AddStep("adjust scale", () => fill.Scale = new Vector2(2));
AddAssert("check sizes unchanged by scale", () => fill.Children.Select(c => c.Drawable.DrawWidth).SequenceEqual(new float[] { 50, 30, 50, 30 }));
}
[Test]
public void TestInitialLoad()
{
var secondarySource = new SecondarySource();
SkinConsumer consumer = null;
AddStep("setup layout", () =>
{
Child = new SkinSourceContainer
{
RelativeSizeAxes = Axes.Both,
Child = new SkinProvidingContainer(secondarySource)
{
RelativeSizeAxes = Axes.Both,
Child = consumer = new SkinConsumer("test", name => new NamedBox("Default Implementation"), source => true)
}
};
});
AddAssert("consumer using override source", () => consumer.Drawable is SecondarySourceBox);
AddAssert("skinchanged only called once", () => consumer.SkinChangedCount == 1);
}
[Test]
public void TestOverride()
{
var secondarySource = new SecondarySource();
SkinConsumer consumer = null;
Container target = null;
AddStep("setup layout", () =>
{
Child = new SkinSourceContainer
{
RelativeSizeAxes = Axes.Both,
Child = target = new SkinProvidingContainer(secondarySource)
{
RelativeSizeAxes = Axes.Both,
}
};
});
AddStep("add permissive", () => target.Add(consumer = new SkinConsumer("test", name => new NamedBox("Default Implementation"), source => true)));
AddAssert("consumer using override source", () => consumer.Drawable is SecondarySourceBox);
AddAssert("skinchanged only called once", () => consumer.SkinChangedCount == 1);
}
[Test]
public void TestSwitchOff()
{
SkinConsumer consumer = null;
SwitchableSkinProvidingContainer target = null;
AddStep("setup layout", () =>
{
Child = new SkinSourceContainer
{
RelativeSizeAxes = Axes.Both,
Child = target = new SwitchableSkinProvidingContainer(new SecondarySource())
{
RelativeSizeAxes = Axes.Both,
}
};
});
AddStep("add permissive", () => target.Add(consumer = new SkinConsumer("test", name => new NamedBox("Default Implementation"), source => true)));
AddAssert("consumer using override source", () => consumer.Drawable is SecondarySourceBox);
AddStep("disable", () => target.Disable());
AddAssert("consumer using base source", () => consumer.Drawable is BaseSourceBox);
}
private class SwitchableSkinProvidingContainer : SkinProvidingContainer
{
private bool allow = true;
protected override bool AllowDrawableLookup(ISkinComponent component) => allow;
public void Disable()
{
allow = false;
TriggerSourceChanged();
}
public SwitchableSkinProvidingContainer(ISkin skin)
: base(skin)
{
}
}
private class ExposedSkinnableDrawable : SkinnableDrawable
{
public new Drawable Drawable => base.Drawable;
public ExposedSkinnableDrawable(string name, Func<ISkinComponent, Drawable> defaultImplementation, Func<ISkinSource, bool> allowFallback = null,
ConfineMode confineMode = ConfineMode.ScaleDownToFit)
: base(new TestSkinComponent(name), defaultImplementation, allowFallback, confineMode)
{
}
}
private class DefaultBox : DrawWidthBox
{
public DefaultBox()
{
RelativeSizeAxes = Axes.Both;
}
}
private class DrawWidthBox : Container
{
private readonly OsuSpriteText text;
public DrawWidthBox()
{
Children = new Drawable[]
{
new Box
{
Colour = Color4.Gray,
RelativeSizeAxes = Axes.Both,
},
text = new OsuSpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
}
};
}
protected override void UpdateAfterChildren()
{
base.UpdateAfterChildren();
text.Text = DrawWidth.ToString(CultureInfo.InvariantCulture);
}
}
private class NamedBox : Container
{
public NamedBox(string name)
{
Children = new Drawable[]
{
new Box
{
Colour = Color4.Black,
RelativeSizeAxes = Axes.Both,
},
new OsuSpriteText
{
Font = OsuFont.Default.With(size: 40),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Text = name
}
};
}
}
private class SkinConsumer : SkinnableDrawable
{
public new Drawable Drawable => base.Drawable;
public int SkinChangedCount { get; private set; }
public SkinConsumer(string name, Func<ISkinComponent, Drawable> defaultImplementation, Func<ISkinSource, bool> allowFallback = null)
: base(new TestSkinComponent(name), defaultImplementation, allowFallback)
{
}
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
{
base.SkinChanged(skin, allowFallback);
SkinChangedCount++;
}
}
private class BaseSourceBox : NamedBox
{
public BaseSourceBox()
: base("Base Source")
{
}
}
private class SecondarySourceBox : NamedBox
{
public SecondarySourceBox()
: base("Secondary Source")
{
}
}
private class SizedSource : ISkin
{
private readonly float size;
public SizedSource(float size)
{
this.size = size;
}
public Drawable GetDrawableComponent(ISkinComponent componentName) =>
componentName.LookupName == "available"
? new DrawWidthBox
{
Colour = Color4.Yellow,
Size = new Vector2(size)
}
: null;
public Texture GetTexture(string componentName) => throw new NotImplementedException();
public SampleChannel GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => throw new NotImplementedException();
}
private class SecondarySource : ISkin
{
public Drawable GetDrawableComponent(ISkinComponent componentName) => new SecondarySourceBox();
public Texture GetTexture(string componentName) => throw new NotImplementedException();
public SampleChannel GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => throw new NotImplementedException();
}
[Cached(typeof(ISkinSource))]
private class SkinSourceContainer : Container, ISkinSource
{
public Drawable GetDrawableComponent(ISkinComponent componentName) => new BaseSourceBox();
public Texture GetTexture(string componentName) => throw new NotImplementedException();
public SampleChannel GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => throw new NotImplementedException();
public event Action SourceChanged
{
add { }
remove { }
}
}
private class TestSkinComponent : ISkinComponent
{
private readonly string name;
public TestSkinComponent(string name)
{
this.name = name;
}
public string ComponentGroup => string.Empty;
public string LookupName => name;
}
}
}

View File

@ -1,19 +1,88 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Timing;
using osu.Game.Screens.Play;
using osuTK;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Gameplay
{
[TestFixture]
public class TestSceneSkipOverlay : OsuTestScene
public class TestSceneSkipOverlay : ManualInputManagerTestScene
{
protected override void LoadComplete()
{
base.LoadComplete();
private SkipOverlay skip;
private int requestCount;
Add(new SkipOverlay(Clock.CurrentTime + 5000));
[SetUp]
public void SetUp() => Schedule(() =>
{
requestCount = 0;
Child = new Container
{
RelativeSizeAxes = Axes.Both,
Clock = new FramedOffsetClock(Clock)
{
Offset = -Clock.CurrentTime,
},
Children = new Drawable[]
{
skip = new SkipOverlay(6000)
{
RequestSeek = _ => requestCount++
}
},
};
});
[Test]
public void TestFadeOnIdle()
{
AddStep("move mouse", () => InputManager.MoveMouseTo(Vector2.Zero));
AddUntilStep("fully visible", () => skip.Children.First().Alpha == 1);
AddUntilStep("wait for fade", () => skip.Children.First().Alpha < 1);
AddStep("move mouse", () => InputManager.MoveMouseTo(skip.ScreenSpaceDrawQuad.Centre));
AddUntilStep("fully visible", () => skip.Children.First().Alpha == 1);
AddUntilStep("wait for fade", () => skip.Children.First().Alpha < 1);
}
[Test]
public void TestClickableAfterFade()
{
AddStep("move mouse", () => InputManager.MoveMouseTo(skip.ScreenSpaceDrawQuad.Centre));
AddUntilStep("wait for fade", () => skip.Children.First().Alpha == 0);
AddStep("click", () => InputManager.Click(MouseButton.Left));
checkRequestCount(1);
}
[Test]
public void TestClickOnlyActuatesOnce()
{
AddStep("move mouse", () => InputManager.MoveMouseTo(skip.ScreenSpaceDrawQuad.Centre));
AddStep("click", () => InputManager.Click(MouseButton.Left));
AddStep("click", () => InputManager.Click(MouseButton.Left));
AddStep("click", () => InputManager.Click(MouseButton.Left));
AddStep("click", () => InputManager.Click(MouseButton.Left));
checkRequestCount(1);
}
[Test]
public void TestDoesntFadeOnMouseDown()
{
AddStep("move mouse", () => InputManager.MoveMouseTo(skip.ScreenSpaceDrawQuad.Centre));
AddStep("button down", () => InputManager.PressButton(MouseButton.Left));
AddUntilStep("wait for overlay disapper", () => !skip.IsAlive);
AddAssert("ensure button didn't disappear", () => skip.Children.First().Alpha > 0);
AddStep("button up", () => InputManager.ReleaseButton(MouseButton.Left));
checkRequestCount(0);
}
private void checkRequestCount(int expected) =>
AddAssert($"request count is {expected}", () => requestCount == expected);
}
}

View File

@ -21,32 +21,38 @@ namespace osu.Game.Tests.Visual.Gameplay
private readonly Container<DrawableStoryboard> storyboardContainer;
private DrawableStoryboard storyboard;
[Cached]
private MusicController musicController = new MusicController();
public TestSceneStoryboard()
{
Clock = new FramedClock();
Add(new Container
AddRange(new Drawable[]
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
musicController,
new Container
{
new Box
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black,
},
storyboardContainer = new Container<DrawableStoryboard>
{
RelativeSizeAxes = Axes.Both,
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black,
},
storyboardContainer = new Container<DrawableStoryboard>
{
RelativeSizeAxes = Axes.Both,
},
},
},
});
Add(new MusicController
{
Origin = Anchor.TopRight,
Anchor = Anchor.TopRight,
State = Visibility.Visible,
new NowPlayingOverlay
{
Origin = Anchor.TopRight,
Anchor = Anchor.TopRight,
State = { Value = Visibility.Visible },
}
});
AddStep("Restart", restart);

View File

@ -0,0 +1,69 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Screens;
using osu.Game.Screens;
using osu.Game.Screens.Menu;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Tests.Visual.Menus
{
[TestFixture]
public abstract class IntroTestScene : OsuTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(StartupScreen),
typeof(IntroScreen),
typeof(OsuScreen),
typeof(IntroTestScene),
};
[Cached]
private OsuLogo logo;
protected IntroTestScene()
{
Drawable introStack = null;
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Depth = float.MaxValue,
Colour = Color4.Black,
},
logo = new OsuLogo
{
Alpha = 0,
RelativePositionAxes = Axes.Both,
Depth = float.MinValue,
Position = new Vector2(0.5f),
}
};
AddStep("restart sequence", () =>
{
logo.FinishTransforms();
logo.IsTracking = false;
introStack?.Expire();
Add(introStack = new OsuScreenStack(CreateScreen())
{
RelativeSizeAxes = Axes.Both,
});
});
}
protected abstract IScreen CreateScreen();
}
}

View File

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

View File

@ -0,0 +1,15 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
using osu.Framework.Screens;
using osu.Game.Screens.Menu;
namespace osu.Game.Tests.Visual.Menus
{
[TestFixture]
public class TestSceneIntroCircles : IntroTestScene
{
protected override IScreen CreateScreen() => new IntroCircles();
}
}

View File

@ -1,54 +0,0 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Timing;
using osu.Game.Screens.Menu;
using osuTK.Graphics;
namespace osu.Game.Tests.Visual.Menus
{
[TestFixture]
public class TestSceneIntroSequence : OsuTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(OsuLogo),
};
public TestSceneIntroSequence()
{
OsuLogo logo;
var rateAdjustClock = new StopwatchClock(true);
var framedClock = new FramedClock(rateAdjustClock);
framedClock.ProcessFrame();
Add(new Container
{
RelativeSizeAxes = Axes.Both,
Clock = framedClock,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black,
},
logo = new OsuLogo
{
Anchor = Anchor.Centre,
}
}
});
AddStep(@"Restart", logo.PlayIntro);
AddSliderStep("Playback speed", 0.0, 2.0, 1, v => rateAdjustClock.Rate = v);
}
}
}

View File

@ -0,0 +1,15 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
using osu.Framework.Screens;
using osu.Game.Screens.Menu;
namespace osu.Game.Tests.Visual.Menus
{
[TestFixture]
public class TestSceneIntroTriangles : IntroTestScene
{
protected override IScreen CreateScreen() => new IntroTriangles();
}
}

View File

@ -33,23 +33,15 @@ namespace osu.Game.Tests.Visual.Menus
[Test]
public void TestInstantLoad()
{
bool logoVisible = false;
// visual only, very impossible to test this using asserts.
AddStep("begin loading", () =>
AddStep("load immediately", () =>
{
loader = new TestLoader();
loader.AllowLoad.Set();
LoadScreen(loader);
});
AddUntilStep("loaded", () =>
{
logoVisible = loader.Logo?.Alpha > 0;
return loader.Logo != null && loader.ScreenLoaded;
});
AddAssert("logo was not visible", () => !logoVisible);
}
[Test]
@ -58,7 +50,7 @@ namespace osu.Game.Tests.Visual.Menus
AddStep("begin loading", () => LoadScreen(loader = new TestLoader()));
AddUntilStep("wait for logo visible", () => loader.Logo?.Alpha > 0);
AddStep("finish loading", () => loader.AllowLoad.Set());
AddAssert("loaded", () => loader.Logo != null && loader.ScreenLoaded);
AddUntilStep("loaded", () => loader.Logo != null && loader.ScreenLoaded);
AddUntilStep("logo gone", () => loader.Logo?.Alpha == 0);
}

View File

@ -0,0 +1,244 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio.Track;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Platform;
using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API;
using osu.Game.Overlays;
using osu.Game.Overlays.Mods;
using osu.Game.Screens;
using osu.Game.Screens.Menu;
using osu.Game.Screens.Play;
using osu.Game.Screens.Select;
using osu.Game.Tests.Beatmaps.IO;
using osuTK;
using osuTK.Graphics;
using osuTK.Input;
using IntroSequence = osu.Game.Configuration.IntroSequence;
namespace osu.Game.Tests.Visual.Menus
{
public class TestSceneScreenNavigation : ManualInputManagerTestScene
{
private const float click_padding = 25;
private GameHost host;
private TestOsuGame game;
private Vector2 backButtonPosition => game.ToScreenSpace(new Vector2(click_padding, game.LayoutRectangle.Bottom - click_padding));
private Vector2 optionsButtonPosition => game.ToScreenSpace(new Vector2(click_padding, click_padding));
[BackgroundDependencyLoader]
private void load(GameHost host)
{
this.host = host;
Child = new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black,
};
}
[SetUpSteps]
public void SetUpSteps()
{
AddStep("Create new game instance", () =>
{
if (game != null)
{
Remove(game);
game.Dispose();
}
game = new TestOsuGame(LocalStorage, API);
game.SetHost(host);
// todo: this can be removed once we can run audio trakcs without a device present
// see https://github.com/ppy/osu/issues/1302
game.LocalConfig.Set(OsuSetting.IntroSequence, IntroSequence.Circles);
Add(game);
});
AddUntilStep("Wait for load", () => game.IsLoaded);
AddUntilStep("Wait for intro", () => game.ScreenStack.CurrentScreen is IntroScreen);
confirmAtMainMenu();
}
[Test]
public void TestExitSongSelectWithEscape()
{
TestSongSelect songSelect = null;
pushAndConfirm(() => songSelect = new TestSongSelect());
AddStep("Show mods overlay", () => songSelect.ModSelectOverlay.Show());
AddAssert("Overlay was shown", () => songSelect.ModSelectOverlay.State.Value == Visibility.Visible);
pushEscape();
AddAssert("Overlay was hidden", () => songSelect.ModSelectOverlay.State.Value == Visibility.Hidden);
exitViaEscapeAndConfirm();
}
[TestCase(true)]
[TestCase(false)]
public void TestSongContinuesAfterExitPlayer(bool withUserPause)
{
Player player = null;
WorkingBeatmap beatmap() => game.Beatmap.Value;
Track track() => beatmap().Track;
pushAndConfirm(() => new TestSongSelect());
AddStep("import beatmap", () => ImportBeatmapTest.LoadOszIntoOsu(game, virtualTrack: true).Wait());
AddUntilStep("wait for selected", () => !game.Beatmap.IsDefault);
if (withUserPause)
AddStep("pause", () => game.Dependencies.Get<MusicController>().Stop());
AddStep("press enter", () => pressAndRelease(Key.Enter));
AddUntilStep("wait for player", () => (player = game.ScreenStack.CurrentScreen as Player) != null);
AddUntilStep("wait for fail", () => player.HasFailed);
AddUntilStep("wait for track stop", () => !track().IsRunning);
AddAssert("Ensure time before preview point", () => track().CurrentTime < beatmap().Metadata.PreviewTime);
pushEscape();
AddUntilStep("wait for track playing", () => track().IsRunning);
AddAssert("Ensure time wasn't reset to preview point", () => track().CurrentTime < beatmap().Metadata.PreviewTime);
}
[Test]
public void TestExitSongSelectWithClick()
{
TestSongSelect songSelect = null;
pushAndConfirm(() => songSelect = new TestSongSelect());
AddStep("Show mods overlay", () => songSelect.ModSelectOverlay.Show());
AddAssert("Overlay was shown", () => songSelect.ModSelectOverlay.State.Value == Visibility.Visible);
AddStep("Move mouse to backButton", () => InputManager.MoveMouseTo(backButtonPosition));
// BackButton handles hover using its child button, so this checks whether or not any of BackButton's children are hovered.
AddUntilStep("Back button is hovered", () => InputManager.HoveredDrawables.Any(d => d.Parent == game.BackButton));
AddStep("Click back button", () => InputManager.Click(MouseButton.Left));
AddUntilStep("Overlay was hidden", () => songSelect.ModSelectOverlay.State.Value == Visibility.Hidden);
exitViaBackButtonAndConfirm();
}
[Test]
public void TestExitMultiWithEscape()
{
pushAndConfirm(() => new Screens.Multi.Multiplayer());
exitViaEscapeAndConfirm();
}
[Test]
public void TestExitMultiWithBackButton()
{
pushAndConfirm(() => new Screens.Multi.Multiplayer());
exitViaBackButtonAndConfirm();
}
[Test]
public void TestOpenOptionsAndExitWithEscape()
{
AddUntilStep("Wait for options to load", () => game.Settings.IsLoaded);
AddStep("Enter menu", () => pressAndRelease(Key.Enter));
AddStep("Move mouse to options overlay", () => InputManager.MoveMouseTo(optionsButtonPosition));
AddStep("Click options overlay", () => InputManager.Click(MouseButton.Left));
AddAssert("Options overlay was opened", () => game.Settings.State.Value == Visibility.Visible);
AddStep("Hide options overlay using escape", () => pressAndRelease(Key.Escape));
AddAssert("Options overlay was closed", () => game.Settings.State.Value == Visibility.Hidden);
}
private void pushAndConfirm(Func<Screen> newScreen)
{
Screen screen = null;
AddStep("Push new screen", () => game.ScreenStack.Push(screen = newScreen()));
AddUntilStep("Wait for new screen", () => game.ScreenStack.CurrentScreen == screen && screen.IsLoaded);
}
private void pushEscape() =>
AddStep("Press escape", () => pressAndRelease(Key.Escape));
private void exitViaEscapeAndConfirm()
{
pushEscape();
confirmAtMainMenu();
}
private void exitViaBackButtonAndConfirm()
{
AddStep("Move mouse to backButton", () => InputManager.MoveMouseTo(backButtonPosition));
AddStep("Click back button", () => InputManager.Click(MouseButton.Left));
confirmAtMainMenu();
}
private void confirmAtMainMenu() => AddUntilStep("Wait for main menu", () => game.ScreenStack.CurrentScreen is MainMenu menu && menu.IsLoaded);
private void pressAndRelease(Key key)
{
InputManager.PressKey(key);
InputManager.ReleaseKey(key);
}
private class TestOsuGame : OsuGame
{
public new ScreenStack ScreenStack => base.ScreenStack;
public new BackButton BackButton => base.BackButton;
public new SettingsPanel Settings => base.Settings;
public new OsuConfigManager LocalConfig => base.LocalConfig;
public new Bindable<WorkingBeatmap> Beatmap => base.Beatmap;
protected override Loader CreateLoader() => new TestLoader();
public TestOsuGame(Storage storage, IAPIProvider api)
{
Storage = storage;
API = api;
}
protected override void LoadComplete()
{
base.LoadComplete();
API.Login("Rhythm Champion", "osu!");
}
}
private class TestSongSelect : PlaySongSelect
{
public ModSelectOverlay ModSelectOverlay => ModSelect;
}
private class TestLoader : Loader
{
protected override ShaderPrecompiler CreateShaderPrecompiler() => new TestShaderPrecompiler();
private class TestShaderPrecompiler : ShaderPrecompiler
{
protected override bool AllLoaded => true;
}
}
}
}

View File

@ -17,13 +17,13 @@ namespace osu.Game.Tests.Visual.Menus
{
typeof(ToolbarButton),
typeof(ToolbarRulesetSelector),
typeof(ToolbarRulesetButton),
typeof(ToolbarRulesetTabButton),
typeof(ToolbarNotificationButton),
};
public TestSceneToolbar()
{
var toolbar = new Toolbar { State = Visibility.Visible };
var toolbar = new Toolbar { State = { Value = Visibility.Visible } };
ToolbarNotificationButton notificationButton = null;
AddStep("create toolbar", () =>

View File

@ -0,0 +1,53 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using osu.Game.Beatmaps;
using osu.Game.Online.Multiplayer;
using osu.Game.Screens.Multi.Match.Components;
using osu.Framework.Graphics;
using osu.Framework.MathUtils;
using osu.Game.Audio;
using osu.Framework.Allocation;
namespace osu.Game.Tests.Visual.Multiplayer
{
[Cached(typeof(IPreviewTrackOwner))]
public class TestSceneMatchBeatmapPanel : MultiplayerTestScene, IPreviewTrackOwner
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(MatchBeatmapPanel)
};
[Resolved]
private PreviewTrackManager previewTrackManager { get; set; }
public TestSceneMatchBeatmapPanel()
{
Add(new MatchBeatmapPanel
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
});
Room.Playlist.Add(new PlaylistItem { Beatmap = new BeatmapInfo { OnlineBeatmapID = 1763072 } });
Room.Playlist.Add(new PlaylistItem { Beatmap = new BeatmapInfo { OnlineBeatmapID = 2101557 } });
Room.Playlist.Add(new PlaylistItem { Beatmap = new BeatmapInfo { OnlineBeatmapID = 1973466 } });
Room.Playlist.Add(new PlaylistItem { Beatmap = new BeatmapInfo { OnlineBeatmapID = 2109801 } });
Room.Playlist.Add(new PlaylistItem { Beatmap = new BeatmapInfo { OnlineBeatmapID = 1922035 } });
}
protected override void LoadComplete()
{
base.LoadComplete();
AddStep("Select random beatmap", () =>
{
Room.CurrentItem.Value = Room.Playlist[RNG.Next(Room.Playlist.Count)];
previewTrackManager.StopAnyPlaying(this);
});
}
}
}

View File

@ -21,7 +21,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
typeof(Info),
typeof(HeaderButton),
typeof(ReadyButton),
typeof(ViewBeatmapButton)
typeof(MatchBeatmapPanel)
};
[BackgroundDependencyLoader]

View File

@ -14,6 +14,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneMatchLeaderboard : MultiplayerTestScene
{
protected override bool UseOnlineAPI => true;
public TestSceneMatchLeaderboard()
{
Room.RoomID.Value = 3;
@ -27,11 +29,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
});
}
[Resolved]
private IAPIProvider api { get; set; }
[BackgroundDependencyLoader]
private void load()
private void load(IAPIProvider api)
{
var req = new GetRoomScoresRequest();
req.Success += v => { };

View File

@ -8,7 +8,6 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.Multiplayer;
@ -37,7 +36,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
settings = new TestRoomSettings
{
RelativeSizeAxes = Axes.Both,
State = Visibility.Visible
State = { Value = Visibility.Visible }
};
Child = settings;
@ -57,7 +56,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("set name", () => Room.Name.Value = "Room name");
AddAssert("button disabled", () => !settings.ApplyButton.Enabled.Value);
AddStep("set beatmap", () => Room.Playlist.Add(new PlaylistItem { Beatmap = new DummyWorkingBeatmap().BeatmapInfo }));
AddStep("set beatmap", () => Room.Playlist.Add(new PlaylistItem { Beatmap = CreateBeatmap(Ruleset.Value).BeatmapInfo }));
AddAssert("button enabled", () => settings.ApplyButton.Enabled.Value);
AddStep("clear name", () => Room.Name.Value = "");

View File

@ -12,6 +12,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
[TestFixture]
public class TestSceneMultiScreen : ScreenTestScene
{
protected override bool UseOnlineAPI => true;
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(Screens.Multi.Multiplayer),

View File

@ -4,9 +4,9 @@
using System;
using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Online.API;
using osu.Game.Overlays;
using osu.Game.Overlays.AccountCreation;
using osu.Game.Users;
@ -25,17 +25,16 @@ namespace osu.Game.Tests.Visual.Online
typeof(AccountCreationScreen),
};
[Cached(typeof(IAPIProvider))]
private DummyAPIAccess api = new DummyAPIAccess();
private readonly Container userPanelArea;
private Bindable<User> localUser;
public TestSceneAccountCreationOverlay()
{
Container userPanelArea;
AccountCreationOverlay accountCreation;
Children = new Drawable[]
{
api,
accountCreation = new AccountCreationOverlay(),
userPanelArea = new Container
{
@ -46,11 +45,18 @@ namespace osu.Game.Tests.Visual.Online
},
};
api.Logout();
api.LocalUser.BindValueChanged(user => { userPanelArea.Child = new UserPanel(user.NewValue) { Width = 200 }; }, true);
AddStep("show", () => accountCreation.Show());
}
AddStep("show", () => accountCreation.State = Visibility.Visible);
AddStep("logout", () => api.Logout());
[BackgroundDependencyLoader]
private void load()
{
API.Logout();
localUser = API.LocalUser.GetBoundCopy();
localUser.BindValueChanged(user => { userPanelArea.Child = new UserPanel(user.NewValue) { Width = 200 }; }, true);
AddStep("logout", API.Logout);
}
}
}

View File

@ -0,0 +1,95 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Overlays.BeatmapSet;
namespace osu.Game.Tests.Visual.Online
{
[TestFixture]
public class TestSceneBeatmapAvailability : OsuTestScene
{
private readonly BeatmapAvailability container;
public TestSceneBeatmapAvailability()
{
Add(container = new BeatmapAvailability());
}
[Test]
public void TestUndownloadableWithLink()
{
AddStep("set undownloadable beatmapset with link", () => container.BeatmapSet = new BeatmapSetInfo
{
OnlineInfo = new BeatmapSetOnlineInfo
{
Availability = new BeatmapSetOnlineAvailability
{
DownloadDisabled = true,
ExternalLink = @"https://osu.ppy.sh",
},
},
});
visiblityAssert(true);
}
[Test]
public void TestUndownloadableNoLink()
{
AddStep("set undownloadable beatmapset without link", () => container.BeatmapSet = new BeatmapSetInfo
{
OnlineInfo = new BeatmapSetOnlineInfo
{
Availability = new BeatmapSetOnlineAvailability
{
DownloadDisabled = true,
},
},
});
visiblityAssert(true);
}
[Test]
public void TestPartsRemovedWithLink()
{
AddStep("set parts-removed beatmapset with link", () => container.BeatmapSet = new BeatmapSetInfo
{
OnlineInfo = new BeatmapSetOnlineInfo
{
Availability = new BeatmapSetOnlineAvailability
{
DownloadDisabled = false,
ExternalLink = @"https://osu.ppy.sh",
},
},
});
visiblityAssert(true);
}
[Test]
public void TestNormal()
{
AddStep("set normal beatmapset", () => container.BeatmapSet = new BeatmapSetInfo
{
OnlineInfo = new BeatmapSetOnlineInfo
{
Availability = new BeatmapSetOnlineAvailability
{
DownloadDisabled = false,
},
},
});
visiblityAssert(false);
}
private void visiblityAssert(bool shown)
{
AddAssert($"is container {(shown ? "visible" : "hidden")}", () => container.Alpha == (shown ? 1 : 0));
}
}
}

View File

@ -0,0 +1,102 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics.UserInterface;
using osu.Game.Beatmaps;
using osu.Game.Overlays.BeatmapSet;
using osu.Game.Rulesets;
using System;
using System.Collections.Generic;
using System.Linq;
namespace osu.Game.Tests.Visual.Online
{
public class TestSceneBeatmapRulesetSelector : OsuTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(BeatmapRulesetSelector),
typeof(BeatmapRulesetTabItem),
};
private readonly TestRulesetSelector selector;
public TestSceneBeatmapRulesetSelector()
{
Add(selector = new TestRulesetSelector());
}
[Resolved]
private RulesetStore rulesets { get; set; }
[Test]
public void TestMultipleRulesetsBeatmapSet()
{
var enabledRulesets = rulesets.AvailableRulesets.Skip(1).Take(2);
AddStep("load multiple rulesets beatmapset", () =>
{
selector.BeatmapSet = new BeatmapSetInfo
{
Beatmaps = enabledRulesets.Select(r => new BeatmapInfo { Ruleset = r }).ToList()
};
});
var tabItems = selector.TabContainer.TabItems;
AddAssert("other rulesets disabled", () => tabItems.Except(tabItems.Where(t => enabledRulesets.Any(r => r.Equals(t.Value)))).All(t => !t.Enabled.Value));
AddAssert("left-most ruleset selected", () => tabItems.First(t => t.Enabled.Value).Active.Value);
}
[Test]
public void TestSingleRulesetBeatmapSet()
{
var enabledRuleset = rulesets.AvailableRulesets.Last();
AddStep("load single ruleset beatmapset", () =>
{
selector.BeatmapSet = new BeatmapSetInfo
{
Beatmaps = new List<BeatmapInfo>
{
new BeatmapInfo
{
Ruleset = enabledRuleset
}
}
};
});
AddAssert("single ruleset selected", () => selector.SelectedTab.Value.Equals(enabledRuleset));
}
[Test]
public void TestEmptyBeatmapSet()
{
AddStep("load empty beatmapset", () => selector.BeatmapSet = new BeatmapSetInfo
{
Beatmaps = new List<BeatmapInfo>()
});
AddAssert("no ruleset selected", () => selector.SelectedTab == null);
AddAssert("all rulesets disabled", () => selector.TabContainer.TabItems.All(t => !t.Enabled.Value));
}
[Test]
public void TestNullBeatmapSet()
{
AddStep("load null beatmapset", () => selector.BeatmapSet = null);
AddAssert("no ruleset selected", () => selector.SelectedTab == null);
AddAssert("all rulesets disabled", () => selector.TabContainer.TabItems.All(t => !t.Enabled.Value));
}
private class TestRulesetSelector : BeatmapRulesetSelector
{
public new TabItem<RulesetInfo> SelectedTab => base.SelectedTab;
public new TabFillFlowContainer TabContainer => base.TabContainer;
}
}
}

View File

@ -19,7 +19,7 @@ namespace osu.Game.Tests.Visual.Online
[TestFixture]
public class TestSceneBeatmapSetOverlay : OsuTestScene
{
private readonly BeatmapSetOverlay overlay;
private readonly TestBeatmapSetOverlay overlay;
public override IReadOnlyList<Type> RequiredTypes => new[]
{
@ -32,375 +32,304 @@ namespace osu.Game.Tests.Visual.Online
typeof(BasicStats),
typeof(BeatmapPicker),
typeof(Details),
typeof(DownloadButton),
typeof(HeaderDownloadButton),
typeof(FavouriteButton),
typeof(Header),
typeof(HeaderButton),
typeof(Info),
typeof(PreviewButton),
typeof(SuccessRate),
typeof(BeatmapAvailability),
typeof(BeatmapRulesetSelector),
typeof(BeatmapRulesetTabItem),
};
protected override bool UseOnlineAPI => true;
public TestSceneBeatmapSetOverlay()
{
Add(overlay = new BeatmapSetOverlay());
Add(overlay = new TestBeatmapSetOverlay());
}
[BackgroundDependencyLoader]
private void load(RulesetStore rulesets)
[Resolved]
private RulesetStore rulesets { get; set; }
[Test]
public void TestLoading()
{
var mania = rulesets.GetRuleset(3);
var taiko = rulesets.GetRuleset(1);
AddStep(@"show loading", () => overlay.ShowBeatmapSet(null));
}
[Test]
public void TestOnline()
{
AddStep(@"show online", () => overlay.FetchAndShowBeatmapSet(55));
}
[Test]
public void TestLocalBeatmaps()
{
AddStep(@"show first", () =>
{
overlay.ShowBeatmapSet(new BeatmapSetInfo
{
OnlineBeatmapSetID = 1235,
Metadata = new BeatmapMetadata
{
Title = @"Lachryma <Re:QueenM>",
Artist = @"Kaneko Chiharu",
Source = @"SOUND VOLTEX III GRAVITY WARS",
Tags = @"sdvx grace the 5th kac original song contest konami bemani",
Title = @"an awesome beatmap",
Artist = @"naru narusegawa",
Source = @"hinata sou",
Tags = @"test tag tag more tag",
Author = new User
{
Username = @"Fresh Chicken",
Id = 3984370,
Username = @"BanchoBot",
Id = 3,
},
},
OnlineInfo = new BeatmapSetOnlineInfo
{
Preview = @"https://b.ppy.sh/preview/415886.mp3",
PlayCount = 681380,
FavouriteCount = 356,
Submitted = new DateTime(2016, 2, 10),
Ranked = new DateTime(2016, 6, 19),
Status = BeatmapSetOnlineStatus.Ranked,
BPM = 236,
Preview = @"https://b.ppy.sh/preview/12345.mp3",
PlayCount = 123,
FavouriteCount = 456,
Submitted = DateTime.Now,
Ranked = DateTime.Now,
BPM = 111,
HasVideo = true,
Covers = new BeatmapSetOnlineCovers
{
Cover = @"https://assets.ppy.sh/beatmaps/415886/covers/cover.jpg?1465651778",
},
HasStoryboard = true,
Covers = new BeatmapSetOnlineCovers(),
},
Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).ToArray() },
Beatmaps = new List<BeatmapInfo>
{
new BeatmapInfo
{
StarDifficulty = 1.36,
Version = @"BASIC",
Ruleset = mania,
StarDifficulty = 9.99,
Version = @"TEST",
Length = 456000,
Ruleset = rulesets.GetRuleset(3),
BaseDifficulty = new BeatmapDifficulty
{
CircleSize = 4,
DrainRate = 6.5f,
OverallDifficulty = 6.5f,
ApproachRate = 5,
CircleSize = 1,
DrainRate = 2.3f,
OverallDifficulty = 4.5f,
ApproachRate = 6,
},
OnlineInfo = new BeatmapOnlineInfo
{
Length = 115000,
CircleCount = 265,
SliderCount = 71,
PlayCount = 47906,
PassCount = 19899,
CircleCount = 111,
SliderCount = 12,
PlayCount = 222,
PassCount = 21,
},
Metrics = new BeatmapMetrics
{
Ratings = Enumerable.Range(0, 11),
Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6),
Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6),
},
},
new BeatmapInfo
{
StarDifficulty = 2.22,
Version = @"NOVICE",
Ruleset = mania,
BaseDifficulty = new BeatmapDifficulty
{
CircleSize = 4,
DrainRate = 7,
OverallDifficulty = 7,
ApproachRate = 5,
},
OnlineInfo = new BeatmapOnlineInfo
{
Length = 118000,
CircleCount = 592,
SliderCount = 62,
PlayCount = 162021,
PassCount = 72116,
},
Metrics = new BeatmapMetrics
{
Ratings = Enumerable.Range(0, 11),
Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6),
Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6),
},
},
new BeatmapInfo
{
StarDifficulty = 3.49,
Version = @"ADVANCED",
Ruleset = mania,
BaseDifficulty = new BeatmapDifficulty
{
CircleSize = 4,
DrainRate = 7.5f,
OverallDifficulty = 7.5f,
ApproachRate = 5,
},
OnlineInfo = new BeatmapOnlineInfo
{
Length = 118000,
CircleCount = 1042,
SliderCount = 79,
PlayCount = 225178,
PassCount = 73001,
},
Metrics = new BeatmapMetrics
{
Ratings = Enumerable.Range(0, 11),
Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6),
Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6),
},
},
new BeatmapInfo
{
StarDifficulty = 4.24,
Version = @"EXHAUST",
Ruleset = mania,
BaseDifficulty = new BeatmapDifficulty
{
CircleSize = 4,
DrainRate = 8,
OverallDifficulty = 8,
ApproachRate = 5,
},
OnlineInfo = new BeatmapOnlineInfo
{
Length = 118000,
CircleCount = 1352,
SliderCount = 69,
PlayCount = 131545,
PassCount = 42703,
},
Metrics = new BeatmapMetrics
{
Ratings = Enumerable.Range(0, 11),
Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6),
Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6),
},
},
new BeatmapInfo
{
StarDifficulty = 5.26,
Version = @"GRAVITY",
Ruleset = mania,
BaseDifficulty = new BeatmapDifficulty
{
CircleSize = 4,
DrainRate = 8.5f,
OverallDifficulty = 8.5f,
ApproachRate = 5,
},
OnlineInfo = new BeatmapOnlineInfo
{
Length = 118000,
CircleCount = 1730,
SliderCount = 115,
PlayCount = 117673,
PassCount = 24241,
},
Metrics = new BeatmapMetrics
{
Ratings = Enumerable.Range(0, 11),
Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6),
Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6),
Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(),
Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(),
},
},
},
});
});
AddStep(@"show second", () =>
downloadAssert(true);
AddStep("show many difficulties", () => overlay.ShowBeatmapSet(createManyDifficultiesBeatmapSet()));
downloadAssert(true);
}
[Test]
public void TestAvailability()
{
AddStep(@"show undownloadable", () =>
{
overlay.ShowBeatmapSet(new BeatmapSetInfo
{
OnlineBeatmapSetID = 1234,
Metadata = new BeatmapMetadata
{
Title = @"undownloadable beatmap",
Artist = @"no one",
Source = @"some source",
Tags = @"another test tag tag more test tags",
Author = new User
{
Username = @"BanchoBot",
Id = 3,
},
},
OnlineInfo = new BeatmapSetOnlineInfo
{
Availability = new BeatmapSetOnlineAvailability
{
DownloadDisabled = true,
ExternalLink = "https://osu.ppy.sh",
},
Preview = @"https://b.ppy.sh/preview/1234.mp3",
PlayCount = 123,
FavouriteCount = 456,
Submitted = DateTime.Now,
Ranked = DateTime.Now,
BPM = 111,
HasVideo = true,
HasStoryboard = true,
Covers = new BeatmapSetOnlineCovers(),
Language = new BeatmapSetOnlineLanguage { Id = 3, Name = "English" },
Genre = new BeatmapSetOnlineGenre { Id = 4, Name = "Rock" },
},
Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).ToArray() },
Beatmaps = new List<BeatmapInfo>
{
new BeatmapInfo
{
StarDifficulty = 5.67,
Version = @"ANOTHER TEST",
Length = 123000,
Ruleset = rulesets.GetRuleset(1),
BaseDifficulty = new BeatmapDifficulty
{
CircleSize = 9,
DrainRate = 8,
OverallDifficulty = 7,
ApproachRate = 6,
},
OnlineInfo = new BeatmapOnlineInfo
{
CircleCount = 123,
SliderCount = 45,
PlayCount = 567,
PassCount = 89,
},
Metrics = new BeatmapMetrics
{
Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(),
Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(),
},
},
},
});
});
downloadAssert(false);
}
[Test]
public void TestMultipleRulesets()
{
AddStep("show multiple rulesets beatmap", () =>
{
var beatmaps = new List<BeatmapInfo>();
foreach (var ruleset in rulesets.AvailableRulesets.Skip(1))
{
beatmaps.Add(new BeatmapInfo
{
Version = ruleset.Name,
Ruleset = ruleset,
BaseDifficulty = new BeatmapDifficulty(),
OnlineInfo = new BeatmapOnlineInfo(),
Metrics = new BeatmapMetrics
{
Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(),
Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(),
},
});
}
overlay.ShowBeatmapSet(new BeatmapSetInfo
{
Metadata = new BeatmapMetadata
{
Title = @"Soumatou Labyrinth",
Artist = @"Yunomi with Momobako&miko",
Tags = @"mmbk.com yuzu__rinrin charlotte",
Title = @"multiple rulesets beatmap",
Artist = @"none",
Author = new User
{
Username = @"komasy",
Id = 1980256,
},
Username = "BanchoBot",
Id = 3,
}
},
OnlineInfo = new BeatmapSetOnlineInfo
{
Preview = @"https://b.ppy.sh/preview/625493.mp3",
PlayCount = 22996,
FavouriteCount = 58,
Submitted = new DateTime(2016, 6, 11),
Ranked = new DateTime(2016, 7, 12),
Status = BeatmapSetOnlineStatus.Pending,
BPM = 160,
HasVideo = false,
Covers = new BeatmapSetOnlineCovers
{
Cover = @"https://assets.ppy.sh/beatmaps/625493/covers/cover.jpg?1499167472",
},
},
Beatmaps = new List<BeatmapInfo>
{
new BeatmapInfo
{
StarDifficulty = 1.40,
Version = @"yzrin's Kantan",
Ruleset = taiko,
BaseDifficulty = new BeatmapDifficulty
{
CircleSize = 2,
DrainRate = 7,
OverallDifficulty = 3,
ApproachRate = 10,
},
OnlineInfo = new BeatmapOnlineInfo
{
Length = 193000,
CircleCount = 262,
SliderCount = 0,
PlayCount = 3952,
PassCount = 1373,
},
Metrics = new BeatmapMetrics
{
Ratings = Enumerable.Range(0, 11),
Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6),
Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6),
},
},
new BeatmapInfo
{
StarDifficulty = 2.23,
Version = @"Futsuu",
Ruleset = taiko,
BaseDifficulty = new BeatmapDifficulty
{
CircleSize = 2,
DrainRate = 6,
OverallDifficulty = 4,
ApproachRate = 10,
},
OnlineInfo = new BeatmapOnlineInfo
{
Length = 193000,
CircleCount = 464,
SliderCount = 0,
PlayCount = 4833,
PassCount = 920,
},
Metrics = new BeatmapMetrics
{
Ratings = Enumerable.Range(0, 11),
Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6),
Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6),
},
},
new BeatmapInfo
{
StarDifficulty = 3.19,
Version = @"Muzukashii",
Ruleset = taiko,
BaseDifficulty = new BeatmapDifficulty
{
CircleSize = 2,
DrainRate = 6,
OverallDifficulty = 5,
ApproachRate = 10,
},
OnlineInfo = new BeatmapOnlineInfo
{
Length = 193000,
CircleCount = 712,
SliderCount = 0,
PlayCount = 4405,
PassCount = 854,
},
Metrics = new BeatmapMetrics
{
Ratings = Enumerable.Range(0, 11),
Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6),
Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6),
},
},
new BeatmapInfo
{
StarDifficulty = 3.97,
Version = @"Charlotte's Oni",
Ruleset = taiko,
BaseDifficulty = new BeatmapDifficulty
{
CircleSize = 5,
DrainRate = 6,
OverallDifficulty = 5.5f,
ApproachRate = 10,
},
OnlineInfo = new BeatmapOnlineInfo
{
Length = 193000,
CircleCount = 943,
SliderCount = 0,
PlayCount = 3950,
PassCount = 693,
},
Metrics = new BeatmapMetrics
{
Ratings = Enumerable.Range(0, 11),
Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6),
Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6),
},
},
new BeatmapInfo
{
StarDifficulty = 5.08,
Version = @"Labyrinth Oni",
Ruleset = taiko,
BaseDifficulty = new BeatmapDifficulty
{
CircleSize = 5,
DrainRate = 5,
OverallDifficulty = 6,
ApproachRate = 10,
},
OnlineInfo = new BeatmapOnlineInfo
{
Length = 193000,
CircleCount = 1068,
SliderCount = 0,
PlayCount = 5856,
PassCount = 1207,
},
Metrics = new BeatmapMetrics
{
Ratings = Enumerable.Range(0, 11),
Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6),
Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6),
},
},
Covers = new BeatmapSetOnlineCovers(),
},
Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).ToArray() },
Beatmaps = beatmaps
});
});
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);
}
[Test]
public void TestHide()
{
AddStep(@"hide", overlay.Hide);
}
[Test]
public void TestShowWithNoReload()
{
AddStep(@"show without reload", overlay.Show);
}
private BeatmapSetInfo createManyDifficultiesBeatmapSet()
{
var beatmaps = new List<BeatmapInfo>();
for (int i = 1; i < 41; i++)
{
beatmaps.Add(new BeatmapInfo
{
OnlineBeatmapID = i * 10,
Version = $"Test #{i}",
Ruleset = Ruleset.Value,
StarDifficulty = 2 + i * 0.1,
BaseDifficulty = new BeatmapDifficulty
{
OverallDifficulty = 3.5f,
},
OnlineInfo = new BeatmapOnlineInfo(),
Metrics = new BeatmapMetrics
{
Fails = Enumerable.Range(1, 100).Select(j => j % 12 - 6).ToArray(),
Retries = Enumerable.Range(-2, 100).Select(j => j % 12 - 6).ToArray(),
},
});
}
return new BeatmapSetInfo
{
OnlineBeatmapSetID = 123,
Metadata = new BeatmapMetadata
{
Title = @"many difficulties beatmap",
Artist = @"none",
Author = new User
{
Username = @"BanchoBot",
Id = 3,
},
},
OnlineInfo = new BeatmapSetOnlineInfo
{
Preview = @"https://b.ppy.sh/preview/123.mp3",
HasVideo = true,
HasStoryboard = true,
Covers = new BeatmapSetOnlineCovers(),
},
Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).ToArray() },
Beatmaps = beatmaps,
};
}
private void downloadAssert(bool shown)
{
AddAssert($"is download button {(shown ? "shown" : "hidden")}", () => overlay.Header.DownloadButtonsVisible == shown);
}
private class TestBeatmapSetOverlay : BeatmapSetOverlay
{
public new Header Header => base.Header;
}
}
}

View File

@ -0,0 +1,69 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.MathUtils;
using osu.Game.Beatmaps;
using osu.Game.Overlays.BeatmapSet;
using osu.Game.Screens.Select.Details;
namespace osu.Game.Tests.Visual.Online
{
public class TestSceneBeatmapSetOverlayDetails : OsuTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(Details)
};
private RatingsExposingDetails details;
[SetUp]
public void Setup() => Schedule(() =>
{
Child = details = new RatingsExposingDetails
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre
};
});
[Test]
public void TestMetrics()
{
var firstSet = createSet();
var secondSet = createSet();
AddStep("set first set", () => details.BeatmapSet = firstSet);
AddAssert("ratings set", () => details.Ratings.Metrics == firstSet.Metrics);
AddStep("set second set", () => details.BeatmapSet = secondSet);
AddAssert("ratings set", () => details.Ratings.Metrics == secondSet.Metrics);
BeatmapSetInfo createSet() => new BeatmapSetInfo
{
Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).Select(_ => RNG.Next(10)).ToArray() },
Beatmaps = new List<BeatmapInfo>
{
new BeatmapInfo
{
Metrics = new BeatmapMetrics
{
Fails = Enumerable.Range(1, 100).Select(_ => RNG.Next(10)).ToArray(),
Retries = Enumerable.Range(-2, 100).Select(_ => RNG.Next(10)).ToArray(),
}
}
}
};
}
private class RatingsExposingDetails : Details
{
public new UserRatings Ratings => base.Ratings;
}
}
}

View File

@ -0,0 +1,82 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.MathUtils;
using osu.Game.Beatmaps;
using osu.Game.Overlays.BeatmapSet;
using osu.Game.Screens.Select.Details;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Tests.Visual.Online
{
public class TestSceneBeatmapSetOverlaySuccessRate : OsuTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(Details)
};
private GraphExposingSuccessRate successRate;
[SetUp]
public void Setup() => Schedule(() =>
{
Child = new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(275, 220),
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Gray,
},
successRate = new GraphExposingSuccessRate
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(275, 220),
Padding = new MarginPadding(20)
}
}
};
});
[Test]
public void TestMetrics()
{
var firstBeatmap = createBeatmap();
var secondBeatmap = createBeatmap();
AddStep("set first set", () => successRate.Beatmap = firstBeatmap);
AddAssert("ratings set", () => successRate.Graph.Metrics == firstBeatmap.Metrics);
AddStep("set second set", () => successRate.Beatmap = secondBeatmap);
AddAssert("ratings set", () => successRate.Graph.Metrics == secondBeatmap.Metrics);
BeatmapInfo createBeatmap() => new BeatmapInfo
{
Metrics = new BeatmapMetrics
{
Fails = Enumerable.Range(1, 100).Select(_ => RNG.Next(10)).ToArray(),
Retries = Enumerable.Range(-2, 100).Select(_ => RNG.Next(10)).ToArray(),
}
};
}
private class GraphExposingSuccessRate : SuccessRate
{
public new FailRetryGraph Graph => base.Graph;
}
}
}

View File

@ -24,8 +24,11 @@ namespace osu.Game.Tests.Visual.Online
typeof(ChangelogListing),
typeof(ChangelogSingleBuild),
typeof(ChangelogBuild),
typeof(Comments),
};
protected override bool UseOnlineAPI => true;
protected override void LoadComplete()
{
base.LoadComplete();
@ -42,7 +45,7 @@ namespace osu.Game.Tests.Visual.Online
{
Version = "2018.712.0",
DisplayVersion = "2018.712.0",
UpdateStream = new APIUpdateStream { Name = "lazer" },
UpdateStream = new APIUpdateStream { Name = OsuGameBase.CLIENT_STREAM_NAME },
ChangelogEntries = new List<APIChangelogEntry>
{
new APIChangelogEntry
@ -65,6 +68,34 @@ namespace osu.Game.Tests.Visual.Online
changelog.ShowListing();
changelog.Show();
});
AddStep(@"Ensure HTML string unescaping", () =>
{
changelog.ShowBuild(new APIChangelogBuild
{
Version = "2019.920.0",
DisplayVersion = "2019.920.0",
UpdateStream = new APIUpdateStream
{
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
{
DisplayName = "Dummy",
OsuUsername = "Dummy",
}
},
}
});
});
}
}
}

View File

@ -70,7 +70,7 @@ namespace osu.Game.Tests.Visual.Online
});
channelTabControl.OnRequestLeave += channel => channelTabControl.RemoveChannel(channel);
channelTabControl.Current.ValueChanged += channel => currentText.Text = "Currently selected channel: " + channel.NewValue.ToString();
channelTabControl.Current.ValueChanged += channel => currentText.Text = "Currently selected channel: " + channel.NewValue;
AddStep("Add random private channel", addRandomPrivateChannel);
AddAssert("There is only one channels", () => channelTabControl.Items.Count() == 2);

View File

@ -1,45 +0,0 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Online.Chat;
using osu.Game.Overlays;
using osu.Game.Overlays.Chat;
using osu.Game.Overlays.Chat.Tabs;
namespace osu.Game.Tests.Visual.Online
{
[Description("Testing chat api and overlay")]
public class TestSceneChatDisplay : OsuTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(ChatOverlay),
typeof(ChatLine),
typeof(DrawableChannel),
typeof(ChannelSelectorTabItem),
typeof(ChannelTabControl),
typeof(ChannelTabItem),
typeof(PrivateChannelTabItem),
typeof(TabCloseButton)
};
[Cached]
private readonly ChannelManager channelManager = new ChannelManager();
[BackgroundDependencyLoader]
private void load()
{
Children = new Drawable[]
{
channelManager,
new ChatOverlay { State = Visibility.Visible }
};
}
}
}

View File

@ -0,0 +1,108 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics.Containers;
using osu.Game.Online.Chat;
using osu.Game.Overlays.Chat;
using osu.Game.Users;
namespace osu.Game.Tests.Visual.Online
{
[TestFixture]
public class TestSceneChatLineTruncation : OsuTestScene
{
private readonly TestChatLineContainer textContainer;
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(ChatLine),
typeof(Message),
typeof(LinkFlowContainer),
typeof(MessageFormatter)
};
public TestSceneChatLineTruncation()
{
Add(textContainer = new TestChatLineContainer
{
Padding = new MarginPadding { Left = 20, Right = 20 },
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
});
}
[BackgroundDependencyLoader]
private void load()
{
testFormatting();
}
private void clear() => AddStep("clear messages", textContainer.Clear);
private void addMessageWithChecks(string text, bool isAction = false, bool isImportant = false, string username = null)
{
int index = textContainer.Count + 1;
var newLine = new ChatLine(new DummyMessage(text, isAction, isImportant, index, username));
textContainer.Add(newLine);
}
private void testFormatting()
{
for (int a = 0; a < 25; a++)
addMessageWithChecks($"Wide {a} character username.", username: new string('w', a));
addMessageWithChecks("Short name with spaces.", username: "sho rt name");
addMessageWithChecks("Long name with spaces.", username: "long name with s p a c e s");
}
private class DummyMessage : Message
{
private static long messageCounter;
internal static readonly User TEST_SENDER_BACKGROUND = new User
{
Username = @"i-am-important",
Id = 42,
Colour = "#250cc9",
};
internal static readonly User TEST_SENDER = new User
{
Username = @"Somebody",
Id = 1,
};
public new DateTimeOffset Timestamp = DateTimeOffset.Now;
public DummyMessage(string text, bool isAction = false, bool isImportant = false, int number = 0, string username = null)
: base(messageCounter++)
{
Content = text;
IsAction = isAction;
Sender = new User
{
Username = username ?? $"user {number}",
Id = number,
Colour = isImportant ? "#250cc9" : null,
};
}
}
private class TestChatLineContainer : FillFlowContainer<ChatLine>
{
protected override int Compare(Drawable x, Drawable y)
{
var xC = (ChatLine)x;
var yC = (ChatLine)y;
return xC.Message.CompareTo(yC.Message);
}
}
}
}

View File

@ -127,6 +127,9 @@ namespace osu.Game.Tests.Visual.Online
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,
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 });
// 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);

View File

@ -0,0 +1,157 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.UserInterface;
using osu.Game.Online.Chat;
using osu.Game.Overlays;
using osu.Game.Overlays.Chat;
using osu.Game.Overlays.Chat.Selection;
using osu.Game.Overlays.Chat.Tabs;
using osu.Game.Users;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Online
{
public class TestSceneChatOverlay : ManualInputManagerTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(ChatLine),
typeof(DrawableChannel),
typeof(ChannelSelectorTabItem),
typeof(ChannelTabControl),
typeof(ChannelTabItem),
typeof(PrivateChannelTabItem),
typeof(TabCloseButton)
};
private TestChatOverlay chatOverlay;
private ChannelManager channelManager;
private readonly Channel channel1 = new Channel(new User()) { Name = "test really long username" };
private readonly Channel channel2 = new Channel(new User()) { Name = "test2" };
[SetUp]
public void Setup()
{
Schedule(() =>
{
ChannelManagerContainer container;
Child = container = new ChannelManagerContainer(new List<Channel> { channel1, channel2 })
{
RelativeSizeAxes = Axes.Both,
};
chatOverlay = container.ChatOverlay;
channelManager = container.ChannelManager;
});
}
[Test]
public void TestHideOverlay()
{
AddAssert("Chat overlay is visible", () => chatOverlay.State.Value == Visibility.Visible);
AddAssert("Selector is visible", () => chatOverlay.SelectionOverlayState == Visibility.Visible);
AddStep("Close chat overlay", () => chatOverlay.Hide());
AddAssert("Chat overlay was hidden", () => chatOverlay.State.Value == Visibility.Hidden);
AddAssert("Channel selection overlay was hidden", () => chatOverlay.SelectionOverlayState == Visibility.Hidden);
}
[Test]
public void TestSelectingChannelClosesSelector()
{
AddAssert("Selector is visible", () => chatOverlay.SelectionOverlayState == Visibility.Visible);
AddStep("Join channel 1", () => channelManager.JoinChannel(channel1));
AddStep("Switch to channel 1", () => clickDrawable(chatOverlay.TabMap[channel1]));
AddAssert("Current channel is channel 1", () => channelManager.CurrentChannel.Value == channel1);
AddAssert("Channel selector was closed", () => chatOverlay.SelectionOverlayState == Visibility.Hidden);
}
[Test]
public void TestCloseChannelWhileSelectorClosed()
{
AddStep("Join channel 1", () => channelManager.JoinChannel(channel1));
AddStep("Join channel 2", () => channelManager.JoinChannel(channel2));
AddStep("Switch to channel 2", () => clickDrawable(chatOverlay.TabMap[channel2]));
AddStep("Close channel 2", () => clickDrawable(((TestChannelTabItem)chatOverlay.TabMap[channel2]).CloseButton.Child));
AddAssert("Selector remained closed", () => chatOverlay.SelectionOverlayState == Visibility.Hidden);
AddAssert("Current channel is channel 1", () => channelManager.CurrentChannel.Value == channel1);
AddStep("Close channel 1", () => clickDrawable(((TestChannelTabItem)chatOverlay.TabMap[channel1]).CloseButton.Child));
AddAssert("Selector is visible", () => chatOverlay.SelectionOverlayState == Visibility.Visible);
}
private void clickDrawable(Drawable d)
{
InputManager.MoveMouseTo(d);
InputManager.Click(MouseButton.Left);
}
private class ChannelManagerContainer : Container
{
public TestChatOverlay ChatOverlay { get; private set; }
[Cached]
public ChannelManager ChannelManager { get; } = new ChannelManager();
private readonly List<Channel> channels;
public ChannelManagerContainer(List<Channel> channels)
{
this.channels = channels;
}
[BackgroundDependencyLoader]
private void load()
{
((BindableList<Channel>)ChannelManager.AvailableChannels).AddRange(channels);
Child = ChatOverlay = new TestChatOverlay { RelativeSizeAxes = Axes.Both, };
ChatOverlay.Show();
}
}
private class TestChatOverlay : ChatOverlay
{
public Visibility SelectionOverlayState => ChannelSelectionOverlay.State.Value;
public new ChannelSelectionOverlay ChannelSelectionOverlay => base.ChannelSelectionOverlay;
protected override ChannelTabControl CreateChannelTabControl() => new TestTabControl();
public IReadOnlyDictionary<Channel, TabItem<Channel>> TabMap => ((TestTabControl)ChannelTabControl).TabMap;
}
private class TestTabControl : ChannelTabControl
{
protected override TabItem<Channel> CreateTabItem(Channel value) => new TestChannelTabItem(value);
public new IReadOnlyDictionary<Channel, TabItem<Channel>> TabMap => base.TabMap;
}
private class TestChannelTabItem : PrivateChannelTabItem
{
public TestChannelTabItem(Channel channel)
: base(channel)
{
}
public new ClickableContainer CloseButton => base.CloseButton;
}
}
}

View File

@ -0,0 +1,59 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using NUnit.Framework;
using osu.Game.Online.API.Requests;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics;
using osu.Game.Overlays.Comments;
namespace osu.Game.Tests.Visual.Online
{
[TestFixture]
public class TestSceneCommentsContainer : OsuTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(CommentsContainer),
typeof(CommentsHeader),
typeof(DrawableComment),
typeof(HeaderButton),
typeof(SortTabControl),
typeof(ShowChildrenButton),
typeof(DeletedChildrenPlaceholder),
typeof(VotePill)
};
protected override bool UseOnlineAPI => true;
public TestSceneCommentsContainer()
{
BasicScrollContainer scrollFlow;
Add(scrollFlow = new BasicScrollContainer
{
RelativeSizeAxes = Axes.Both,
});
AddStep("Big Black comments", () =>
{
scrollFlow.Clear();
scrollFlow.Add(new CommentsContainer(CommentableType.Beatmapset, 41823));
});
AddStep("Airman comments", () =>
{
scrollFlow.Clear();
scrollFlow.Add(new CommentsContainer(CommentableType.Beatmapset, 24313));
});
AddStep("lazer build comments", () =>
{
scrollFlow.Clear();
scrollFlow.Add(new CommentsContainer(CommentableType.Build, 4772));
});
}
}
}

View File

@ -0,0 +1,39 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Bindables;
using osu.Game.Overlays.Comments;
namespace osu.Game.Tests.Visual.Online
{
[TestFixture]
public class TestSceneCommentsHeader : OsuTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(CommentsHeader),
typeof(HeaderButton),
typeof(SortTabControl),
};
private readonly Bindable<CommentsSortCriteria> sort = new Bindable<CommentsSortCriteria>();
private readonly BindableBool showDeleted = new BindableBool();
public TestSceneCommentsHeader()
{
Add(new CommentsHeader
{
Sort = { BindTarget = sort },
ShowDeleted = { BindTarget = showDeleted }
});
AddStep("Trigger ShowDeleted", () => showDeleted.Value = !showDeleted.Value);
AddStep("Select old", () => sort.Value = CommentsSortCriteria.Old);
AddStep("Select new", () => sort.Value = CommentsSortCriteria.New);
AddStep("Select top", () => sort.Value = CommentsSortCriteria.Top);
}
}
}

View File

@ -0,0 +1,158 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Beatmaps;
using osu.Game.Online;
using osu.Game.Overlays.Direct;
using osu.Game.Rulesets.Osu;
using osu.Game.Tests.Resources;
using osuTK;
namespace osu.Game.Tests.Visual.Online
{
public class TestSceneDirectDownloadButton : OsuTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(PanelDownloadButton)
};
private TestDownloadButton downloadButton;
[Resolved]
private BeatmapManager beatmaps { get; set; }
[Test]
public void TestDownloadableBeatmap()
{
createButton(true);
assertEnabled(true);
}
[Test]
public void TestUndownloadableBeatmap()
{
createButton(false);
assertEnabled(false);
}
[Test]
public void TestDownloadState()
{
AddUntilStep("ensure manager loaded", () => beatmaps != null);
ensureSoleilyRemoved();
createButtonWithBeatmap(createSoleily());
AddAssert("button state not downloaded", () => downloadButton.DownloadState == DownloadState.NotDownloaded);
AddStep("import soleily", () => beatmaps.Import(new[] { TestResources.GetTestBeatmapForImport() }));
AddUntilStep("wait for beatmap import", () => beatmaps.GetAllUsableBeatmapSets().Any(b => b.OnlineBeatmapSetID == 241526));
createButtonWithBeatmap(createSoleily());
AddAssert("button state downloaded", () => downloadButton.DownloadState == DownloadState.LocallyAvailable);
ensureSoleilyRemoved();
AddAssert("button state not downloaded", () => downloadButton.DownloadState == DownloadState.NotDownloaded);
}
private void ensureSoleilyRemoved()
{
AddStep("remove soleily", () =>
{
var beatmap = beatmaps.QueryBeatmapSet(b => b.OnlineBeatmapSetID == 241526);
if (beatmap != null) beatmaps.Delete(beatmap);
});
}
private void assertEnabled(bool enabled)
{
AddAssert($"button {(enabled ? "enabled" : "disabled")}", () => downloadButton.DownloadEnabled == enabled);
}
private BeatmapSetInfo createSoleily()
{
return new BeatmapSetInfo
{
ID = 1,
OnlineBeatmapSetID = 241526,
OnlineInfo = new BeatmapSetOnlineInfo
{
Availability = new BeatmapSetOnlineAvailability
{
DownloadDisabled = false,
ExternalLink = string.Empty,
},
},
};
}
private void createButtonWithBeatmap(BeatmapSetInfo beatmap)
{
AddStep("create button", () =>
{
Child = downloadButton = new TestDownloadButton(beatmap)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(75, 50),
};
});
}
private void createButton(bool downloadable)
{
AddStep("create button", () =>
{
Child = downloadButton = new TestDownloadButton(downloadable ? getDownloadableBeatmapSet() : getUndownloadableBeatmapSet())
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(75, 50),
};
});
}
private BeatmapSetInfo getDownloadableBeatmapSet()
{
var normal = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo).BeatmapSetInfo;
normal.OnlineInfo.HasVideo = true;
normal.OnlineInfo.HasStoryboard = true;
return normal;
}
private BeatmapSetInfo getUndownloadableBeatmapSet()
{
var beatmap = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo).BeatmapSetInfo;
beatmap.Metadata.Artist = "test";
beatmap.Metadata.Title = "undownloadable";
beatmap.Metadata.AuthorString = "test";
beatmap.OnlineInfo.HasVideo = true;
beatmap.OnlineInfo.HasStoryboard = true;
beatmap.OnlineInfo.Availability = new BeatmapSetOnlineAvailability
{
DownloadDisabled = true,
ExternalLink = "http://osu.ppy.sh",
};
return beatmap;
}
private class TestDownloadButton : PanelDownloadButton
{
public new bool DownloadEnabled => base.DownloadEnabled;
public DownloadState DownloadState => State.Value;
public TestDownloadButton(BeatmapSetInfo beatmapSet, bool noVideo = false)
: base(beatmapSet, noVideo)
{
}
}
}
}

View File

@ -3,10 +3,8 @@
using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Game.Beatmaps;
using osu.Game.Overlays;
using osu.Game.Rulesets;
namespace osu.Game.Tests.Visual.Online
{
@ -14,7 +12,8 @@ namespace osu.Game.Tests.Visual.Online
public class TestSceneDirectOverlay : OsuTestScene
{
private DirectOverlay direct;
private RulesetStore rulesets;
protected override bool UseOnlineAPI => true;
protected override void LoadComplete()
{
@ -25,18 +24,11 @@ namespace osu.Game.Tests.Visual.Online
AddStep(@"toggle", direct.ToggleVisibility);
AddStep(@"result counts", () => direct.ResultAmounts = new DirectOverlay.ResultCounts(1, 4, 13));
}
[BackgroundDependencyLoader]
private void load(RulesetStore rulesets)
{
this.rulesets = rulesets;
AddStep(@"trigger disabled", () => Ruleset.Disabled = !Ruleset.Disabled);
}
private void newBeatmaps()
{
var ruleset = rulesets.GetRuleset(0);
direct.BeatmapSets = new[]
{
new BeatmapSetInfo
@ -65,7 +57,7 @@ namespace osu.Game.Tests.Visual.Online
{
new BeatmapInfo
{
Ruleset = ruleset,
Ruleset = Ruleset.Value,
StarDifficulty = 5.35f,
Metadata = new BeatmapMetadata(),
},
@ -97,7 +89,7 @@ namespace osu.Game.Tests.Visual.Online
{
new BeatmapInfo
{
Ruleset = ruleset,
Ruleset = Ruleset.Value,
StarDifficulty = 5.81f,
Metadata = new BeatmapMetadata(),
},
@ -129,23 +121,23 @@ namespace osu.Game.Tests.Visual.Online
{
new BeatmapInfo
{
Ruleset = ruleset,
Ruleset = Ruleset.Value,
StarDifficulty = 0.9f,
Metadata = new BeatmapMetadata(),
},
new BeatmapInfo
{
Ruleset = ruleset,
Ruleset = Ruleset.Value,
StarDifficulty = 1.1f,
},
new BeatmapInfo
{
Ruleset = ruleset,
Ruleset = Ruleset.Value,
StarDifficulty = 2.02f,
},
new BeatmapInfo
{
Ruleset = ruleset,
Ruleset = Ruleset.Value,
StarDifficulty = 3.49f,
},
},
@ -176,43 +168,43 @@ namespace osu.Game.Tests.Visual.Online
{
new BeatmapInfo
{
Ruleset = ruleset,
Ruleset = Ruleset.Value,
StarDifficulty = 1.26f,
Metadata = new BeatmapMetadata(),
},
new BeatmapInfo
{
Ruleset = ruleset,
Ruleset = Ruleset.Value,
StarDifficulty = 2.01f,
},
new BeatmapInfo
{
Ruleset = ruleset,
Ruleset = Ruleset.Value,
StarDifficulty = 2.87f,
},
new BeatmapInfo
{
Ruleset = ruleset,
Ruleset = Ruleset.Value,
StarDifficulty = 3.76f,
},
new BeatmapInfo
{
Ruleset = ruleset,
Ruleset = Ruleset.Value,
StarDifficulty = 3.93f,
},
new BeatmapInfo
{
Ruleset = ruleset,
Ruleset = Ruleset.Value,
StarDifficulty = 4.37f,
},
new BeatmapInfo
{
Ruleset = ruleset,
Ruleset = Ruleset.Value,
StarDifficulty = 5.13f,
},
new BeatmapInfo
{
Ruleset = ruleset,
Ruleset = Ruleset.Value,
StarDifficulty = 5.42f,
},
},

View File

@ -6,9 +6,10 @@ using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Overlays.Direct;
using osu.Game.Rulesets.Osu;
using osu.Game.Tests.Beatmaps;
using osu.Game.Rulesets;
using osu.Game.Users;
using osuTK;
namespace osu.Game.Tests.Visual.Online
@ -22,25 +23,115 @@ namespace osu.Game.Tests.Visual.Online
typeof(IconPill)
};
[BackgroundDependencyLoader]
private void load()
private BeatmapSetInfo getUndownloadableBeatmapSet() => new BeatmapSetInfo
{
var beatmap = new TestWorkingBeatmap(new OsuRuleset().RulesetInfo, null);
beatmap.BeatmapSetInfo.OnlineInfo.HasVideo = true;
beatmap.BeatmapSetInfo.OnlineInfo.HasStoryboard = true;
Child = new FillFlowContainer
OnlineBeatmapSetID = 123,
Metadata = new BeatmapMetadata
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Padding = new MarginPadding(20),
Spacing = new Vector2(0, 20),
Children = new Drawable[]
Title = "undownloadable beatmap",
Artist = "test",
Source = "more tests",
Author = new User
{
new DirectGridPanel(beatmap.BeatmapSetInfo),
new DirectListPanel(beatmap.BeatmapSetInfo)
Username = "BanchoBot",
Id = 3,
},
},
OnlineInfo = new BeatmapSetOnlineInfo
{
Availability = new BeatmapSetOnlineAvailability
{
DownloadDisabled = true,
},
Preview = @"https://b.ppy.sh/preview/12345.mp3",
PlayCount = 123,
FavouriteCount = 456,
BPM = 111,
HasVideo = true,
HasStoryboard = true,
Covers = new BeatmapSetOnlineCovers(),
},
Beatmaps = new List<BeatmapInfo>
{
new BeatmapInfo
{
Ruleset = Ruleset.Value,
Version = "Test",
StarDifficulty = 6.42,
}
}
};
private BeatmapSetInfo getManyDifficultiesBeatmapSet(RulesetStore rulesets)
{
var beatmaps = new List<BeatmapInfo>();
for (int i = 0; i < 100; i++)
{
beatmaps.Add(new BeatmapInfo
{
Ruleset = rulesets.GetRuleset(i % 4),
StarDifficulty = 2 + i % 4 * 2,
BaseDifficulty = new BeatmapDifficulty
{
OverallDifficulty = 3.5f,
}
});
}
return new BeatmapSetInfo
{
OnlineBeatmapSetID = 1,
Metadata = new BeatmapMetadata
{
Title = "many difficulties beatmap",
Artist = "test",
Author = new User
{
Username = "BanchoBot",
Id = 3,
}
},
OnlineInfo = new BeatmapSetOnlineInfo
{
HasVideo = true,
HasStoryboard = true,
Covers = new BeatmapSetOnlineCovers(),
},
Beatmaps = beatmaps,
};
}
[BackgroundDependencyLoader]
private void load(RulesetStore rulesets)
{
var normal = CreateWorkingBeatmap(Ruleset.Value).BeatmapSetInfo;
normal.OnlineInfo.HasVideo = true;
normal.OnlineInfo.HasStoryboard = true;
var undownloadable = getUndownloadableBeatmapSet();
var manyDifficulties = getManyDifficultiesBeatmapSet(rulesets);
Child = new BasicScrollContainer
{
RelativeSizeAxes = Axes.Both,
Child = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Full,
Padding = new MarginPadding(20),
Spacing = new Vector2(5, 20),
Children = new Drawable[]
{
new DirectGridPanel(normal),
new DirectGridPanel(undownloadable),
new DirectGridPanel(manyDifficulties),
new DirectListPanel(normal),
new DirectListPanel(undownloadable),
new DirectListPanel(manyDifficulties),
},
},
};
}
}

View File

@ -0,0 +1,54 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Overlays.BeatmapSet.Buttons;
using osuTK;
namespace osu.Game.Tests.Visual.Online
{
public class TestSceneFavouriteButton : OsuTestScene
{
private FavouriteButton favourite;
[SetUpSteps]
public void SetUpSteps()
{
AddStep("create button", () => Child = favourite = new FavouriteButton
{
RelativeSizeAxes = Axes.None,
Size = new Vector2(50),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
});
}
[Test]
public void TestLoggedOutIn()
{
AddStep("set valid beatmap", () => favourite.BeatmapSet.Value = new BeatmapSetInfo { OnlineBeatmapSetID = 88 });
AddStep("log out", () => API.Logout());
checkEnabled(false);
AddStep("log in", () => API.Login("test", "test"));
checkEnabled(true);
}
[Test]
public void TestBeatmapChange()
{
AddStep("log in", () => API.Login("test", "test"));
AddStep("set valid beatmap", () => favourite.BeatmapSet.Value = new BeatmapSetInfo { OnlineBeatmapSetID = 88 });
checkEnabled(true);
AddStep("set invalid beatmap", () => favourite.BeatmapSet.Value = new BeatmapSetInfo());
checkEnabled(false);
}
private void checkEnabled(bool expected)
{
AddAssert("is " + (expected ? "enabled" : "disabled"), () => favourite.Enabled.Value == expected);
}
}
}

View File

@ -18,8 +18,24 @@ namespace osu.Game.Tests.Visual.Online
{
base.LoadComplete();
int fireCount = 0;
Add(overlay = new TestFullscreenOverlay());
AddStep(@"toggle", overlay.ToggleVisibility);
overlay.State.ValueChanged += _ => fireCount++;
AddStep(@"show", overlay.Show);
AddAssert("fire count 1", () => fireCount == 1);
AddStep(@"show again", overlay.Show);
// this logic is specific to FullscreenOverlay
AddAssert("fire count 2", () => fireCount == 2);
AddStep(@"hide", overlay.Hide);
AddAssert("fire count 3", () => fireCount == 3);
}
private class TestFullscreenOverlay : FullscreenOverlay

View File

@ -5,9 +5,9 @@ using System;
using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Overlays.Profile.Sections;
using osu.Game.Overlays.Profile.Sections.Historical;
using osu.Game.Users;
@ -17,14 +17,15 @@ namespace osu.Game.Tests.Visual.Online
[TestFixture]
public class TestSceneHistoricalSection : OsuTestScene
{
public override IReadOnlyList<Type> RequiredTypes =>
new[]
{
typeof(HistoricalSection),
typeof(PaginatedMostPlayedBeatmapContainer),
typeof(DrawableMostPlayedRow),
typeof(DrawableProfileRow)
};
protected override bool UseOnlineAPI => true;
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(HistoricalSection),
typeof(PaginatedMostPlayedBeatmapContainer),
typeof(DrawableMostPlayedBeatmap),
typeof(DrawableProfileRow)
};
public TestSceneHistoricalSection()
{
@ -36,7 +37,7 @@ namespace osu.Game.Tests.Visual.Online
Colour = OsuColour.Gray(0.2f)
});
Add(new ScrollContainer
Add(new OsuScrollContainer
{
RelativeSizeAxes = Axes.Both,
Child = section = new HistoricalSection(),

View File

@ -0,0 +1,246 @@
// 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.Kudosu;
using System.Collections.Generic;
using System;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics;
using osu.Framework.Allocation;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics;
using osu.Game.Online.API.Requests.Responses;
using osu.Framework.Extensions.IEnumerableExtensions;
namespace osu.Game.Tests.Visual.Online
{
public class TestSceneKudosuHistory : OsuTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(DrawableKudosuHistoryItem),
};
private readonly Box background;
public TestSceneKudosuHistory()
{
FillFlowContainer<DrawableKudosuHistoryItem> content;
AddRange(new Drawable[]
{
background = new Box
{
RelativeSizeAxes = Axes.Both,
},
content = new FillFlowContainer<DrawableKudosuHistoryItem>
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.X,
Width = 0.7f,
AutoSizeAxes = Axes.Y,
}
});
items.ForEach(t => content.Add(new DrawableKudosuHistoryItem(t)));
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
background.Colour = colours.GreySeafoam;
}
private readonly IEnumerable<APIKudosuHistory> items = new[]
{
new APIKudosuHistory
{
Amount = 10,
CreatedAt = new DateTimeOffset(new DateTime(2011, 11, 11)),
Source = KudosuSource.DenyKudosu,
Action = KudosuAction.Reset,
Post = new APIKudosuHistory.ModdingPost
{
Title = @"Random post 1",
Url = @"https://osu.ppy.sh/b/1234",
},
Giver = new APIKudosuHistory.KudosuGiver
{
Username = @"Username1",
Url = @"https://osu.ppy.sh/u/1234"
}
},
new APIKudosuHistory
{
Amount = 5,
CreatedAt = new DateTimeOffset(new DateTime(2012, 10, 11)),
Source = KudosuSource.Forum,
Action = KudosuAction.Give,
Post = new APIKudosuHistory.ModdingPost
{
Title = @"Random post 2",
Url = @"https://osu.ppy.sh/b/1234",
},
Giver = new APIKudosuHistory.KudosuGiver
{
Username = @"Username2",
Url = @"https://osu.ppy.sh/u/1234"
}
},
new APIKudosuHistory
{
Amount = 8,
CreatedAt = new DateTimeOffset(new DateTime(2013, 9, 11)),
Source = KudosuSource.Forum,
Action = KudosuAction.Reset,
Post = new APIKudosuHistory.ModdingPost
{
Title = @"Random post 3",
Url = @"https://osu.ppy.sh/b/1234",
},
Giver = new APIKudosuHistory.KudosuGiver
{
Username = @"Username3",
Url = @"https://osu.ppy.sh/u/1234"
}
},
new APIKudosuHistory
{
Amount = 7,
CreatedAt = new DateTimeOffset(new DateTime(2014, 8, 11)),
Source = KudosuSource.Forum,
Action = KudosuAction.Revoke,
Post = new APIKudosuHistory.ModdingPost
{
Title = @"Random post 4",
Url = @"https://osu.ppy.sh/b/1234",
},
Giver = new APIKudosuHistory.KudosuGiver
{
Username = @"Username4",
Url = @"https://osu.ppy.sh/u/1234"
}
},
new APIKudosuHistory
{
Amount = 100,
CreatedAt = new DateTimeOffset(new DateTime(2015, 7, 11)),
Source = KudosuSource.Vote,
Action = KudosuAction.Give,
Post = new APIKudosuHistory.ModdingPost
{
Title = @"Random post 5",
Url = @"https://osu.ppy.sh/b/1234",
},
Giver = new APIKudosuHistory.KudosuGiver
{
Username = @"Username5",
Url = @"https://osu.ppy.sh/u/1234"
}
},
new APIKudosuHistory
{
Amount = 20,
CreatedAt = new DateTimeOffset(new DateTime(2016, 6, 11)),
Source = KudosuSource.Vote,
Action = KudosuAction.Reset,
Post = new APIKudosuHistory.ModdingPost
{
Title = @"Random post 6",
Url = @"https://osu.ppy.sh/b/1234",
},
Giver = new APIKudosuHistory.KudosuGiver
{
Username = @"Username6",
Url = @"https://osu.ppy.sh/u/1234"
}
},
new APIKudosuHistory
{
Amount = 11,
CreatedAt = new DateTimeOffset(new DateTime(2016, 6, 11)),
Source = KudosuSource.AllowKudosu,
Action = KudosuAction.Give,
Post = new APIKudosuHistory.ModdingPost
{
Title = @"Random post 7",
Url = @"https://osu.ppy.sh/b/1234",
},
Giver = new APIKudosuHistory.KudosuGiver
{
Username = @"Username7",
Url = @"https://osu.ppy.sh/u/1234"
}
},
new APIKudosuHistory
{
Amount = 24,
CreatedAt = new DateTimeOffset(new DateTime(2014, 6, 11)),
Source = KudosuSource.Delete,
Action = KudosuAction.Reset,
Post = new APIKudosuHistory.ModdingPost
{
Title = @"Random post 8",
Url = @"https://osu.ppy.sh/b/1234",
},
Giver = new APIKudosuHistory.KudosuGiver
{
Username = @"Username8",
Url = @"https://osu.ppy.sh/u/1234"
}
},
new APIKudosuHistory
{
Amount = 12,
CreatedAt = new DateTimeOffset(new DateTime(2016, 6, 11)),
Source = KudosuSource.Restore,
Action = KudosuAction.Give,
Post = new APIKudosuHistory.ModdingPost
{
Title = @"Random post 9",
Url = @"https://osu.ppy.sh/b/1234",
},
Giver = new APIKudosuHistory.KudosuGiver
{
Username = @"Username9",
Url = @"https://osu.ppy.sh/u/1234"
}
},
new APIKudosuHistory
{
Amount = 2,
CreatedAt = new DateTimeOffset(new DateTime(2012, 6, 11)),
Source = KudosuSource.Recalculate,
Action = KudosuAction.Give,
Post = new APIKudosuHistory.ModdingPost
{
Title = @"Random post 10",
Url = @"https://osu.ppy.sh/b/1234",
},
Giver = new APIKudosuHistory.KudosuGiver
{
Username = @"Username10",
Url = @"https://osu.ppy.sh/u/1234"
}
},
new APIKudosuHistory
{
Amount = 32,
CreatedAt = new DateTimeOffset(new DateTime(2019, 8, 11)),
Source = KudosuSource.Recalculate,
Action = KudosuAction.Reset,
Post = new APIKudosuHistory.ModdingPost
{
Title = @"Random post 11",
Url = @"https://osu.ppy.sh/b/1234",
},
Giver = new APIKudosuHistory.KudosuGiver
{
Username = @"Username11",
Url = @"https://osu.ppy.sh/u/1234"
}
}
};
}
}

View File

@ -0,0 +1,36 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Overlays.BeatmapSet;
using System;
using System.Collections.Generic;
using osu.Framework.Graphics;
using osu.Framework.Bindables;
using osu.Game.Screens.Select.Leaderboards;
namespace osu.Game.Tests.Visual.Online
{
public class TestSceneLeaderboardScopeSelector : OsuTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(LeaderboardScopeSelector),
};
public TestSceneLeaderboardScopeSelector()
{
Bindable<BeatmapLeaderboardScope> scope = new Bindable<BeatmapLeaderboardScope>();
Add(new LeaderboardScopeSelector
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Current = { BindTarget = scope }
});
AddStep(@"Select global", () => scope.Value = BeatmapLeaderboardScope.Global);
AddStep(@"Select country", () => scope.Value = BeatmapLeaderboardScope.Country);
AddStep(@"Select friend", () => scope.Value = BeatmapLeaderboardScope.Friend);
}
}
}

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