summaryrefslogtreecommitdiff
path: root/src/SMAPI.Tests
diff options
context:
space:
mode:
Diffstat (limited to 'src/SMAPI.Tests')
-rw-r--r--src/SMAPI.Tests/Core/AssetNameTests.cs10
-rw-r--r--src/SMAPI.Tests/Core/InterfaceProxyTests.cs12
-rw-r--r--src/SMAPI.Tests/Core/ModResolverTests.cs31
-rw-r--r--src/SMAPI.Tests/Core/TranslationTests.cs83
-rw-r--r--src/SMAPI.Tests/Sample.cs2
-rw-r--r--src/SMAPI.Tests/Utilities/KeybindListTests.cs22
-rw-r--r--src/SMAPI.Tests/Utilities/PathUtilitiesTests.cs183
-rw-r--r--src/SMAPI.Tests/Utilities/SDateTests.cs19
-rw-r--r--src/SMAPI.Tests/WikiClient/ChangeDescriptorTests.cs24
9 files changed, 210 insertions, 176 deletions
diff --git a/src/SMAPI.Tests/Core/AssetNameTests.cs b/src/SMAPI.Tests/Core/AssetNameTests.cs
index ef8a08ef..a1712726 100644
--- a/src/SMAPI.Tests/Core/AssetNameTests.cs
+++ b/src/SMAPI.Tests/Core/AssetNameTests.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
using System;
using System.Collections.Generic;
using FluentAssertions;
@@ -28,7 +26,7 @@ namespace SMAPI.Tests.Core
[TestCase("Characters/Dialogue/Abigail.fr-FR", "Characters/Dialogue/Abigail", "fr-FR", LocalizedContentManager.LanguageCode.fr)]
[TestCase("Characters/Dialogue\\Abigail.fr-FR", "Characters/Dialogue/Abigail.fr-FR", null, null)]
[TestCase("Characters/Dialogue/Abigail.fr-FR", "Characters/Dialogue/Abigail", "fr-FR", LocalizedContentManager.LanguageCode.fr)]
- public void Constructor_Valid(string name, string expectedBaseName, string expectedLocale, LocalizedContentManager.LanguageCode? expectedLanguageCode)
+ public void Constructor_Valid(string name, string expectedBaseName, string? expectedLocale, LocalizedContentManager.LanguageCode? expectedLanguageCode)
{
// arrange
name = PathUtilities.NormalizeAssetName(name);
@@ -55,13 +53,13 @@ namespace SMAPI.Tests.Core
[TestCase(" ")]
[TestCase("\t")]
[TestCase(" \t ")]
- public void Constructor_NullOrWhitespace(string name)
+ public void Constructor_NullOrWhitespace(string? name)
{
// act
- ArgumentException exception = Assert.Throws<ArgumentException>(() => _ = AssetName.Parse(name, null));
+ ArgumentException exception = Assert.Throws<ArgumentException>(() => _ = AssetName.Parse(name!, _ => null))!;
// assert
- exception!.ParamName.Should().Be("rawName");
+ exception.ParamName.Should().Be("rawName");
exception.Message.Should().Be("The asset name can't be null or empty. (Parameter 'rawName')");
}
diff --git a/src/SMAPI.Tests/Core/InterfaceProxyTests.cs b/src/SMAPI.Tests/Core/InterfaceProxyTests.cs
index 1bf2ed68..0b4919ed 100644
--- a/src/SMAPI.Tests/Core/InterfaceProxyTests.cs
+++ b/src/SMAPI.Tests/Core/InterfaceProxyTests.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
@@ -41,7 +39,7 @@ namespace SMAPI.Tests.Core
public void CanProxy_EventField()
{
// arrange
- var providerMod = new ProviderMod();
+ ProviderMod providerMod = new();
object implementation = providerMod.GetModApi();
int expectedValue = this.Random.Next();
@@ -61,7 +59,7 @@ namespace SMAPI.Tests.Core
public void CanProxy_EventProperty()
{
// arrange
- var providerMod = new ProviderMod();
+ ProviderMod providerMod = new();
object implementation = providerMod.GetModApi();
int expectedValue = this.Random.Next();
@@ -86,7 +84,7 @@ namespace SMAPI.Tests.Core
public void CanProxy_Properties(string setVia)
{
// arrange
- var providerMod = new ProviderMod();
+ ProviderMod providerMod = new();
object implementation = providerMod.GetModApi();
int expectedNumber = this.Random.Next();
int expectedObject = this.Random.Next();
@@ -317,13 +315,13 @@ namespace SMAPI.Tests.Core
/// <summary>Get a property value from an instance.</summary>
/// <param name="parent">The instance whose property to read.</param>
/// <param name="name">The property name.</param>
- private object GetPropertyValue(object parent, string name)
+ private object? GetPropertyValue(object parent, string name)
{
if (parent is null)
throw new ArgumentNullException(nameof(parent));
Type type = parent.GetType();
- PropertyInfo property = type.GetProperty(name);
+ PropertyInfo? property = type.GetProperty(name);
if (property is null)
throw new InvalidOperationException($"The '{type.FullName}' type has no public property named '{name}'.");
diff --git a/src/SMAPI.Tests/Core/ModResolverTests.cs b/src/SMAPI.Tests/Core/ModResolverTests.cs
index e1b56559..bd621bbf 100644
--- a/src/SMAPI.Tests/Core/ModResolverTests.cs
+++ b/src/SMAPI.Tests/Core/ModResolverTests.cs
@@ -145,7 +145,7 @@ namespace SMAPI.Tests.Core
{
// arrange
Mock<IModMetadata> mock = this.GetMetadata("Mod A", Array.Empty<string>(), allowStatusChange: true);
- this.SetupMetadataForValidation(mock, new ModDataRecordVersionedFields
+ this.SetupMetadataForValidation(mock, new ModDataRecordVersionedFields(this.GetModDataRecord())
{
Status = ModStatus.AssumeBroken
});
@@ -216,9 +216,9 @@ namespace SMAPI.Tests.Core
File.WriteAllText(Path.Combine(modFolder, manifest.EntryDll!), "");
// arrange
- Mock<IModMetadata> mock = new Mock<IModMetadata>(MockBehavior.Strict);
+ Mock<IModMetadata> mock = new(MockBehavior.Strict);
mock.Setup(p => p.Status).Returns(ModMetadataStatus.Found);
- mock.Setup(p => p.DataRecord).Returns(() => null);
+ mock.Setup(p => p.DataRecord).Returns(this.GetModDataRecordVersionedFields());
mock.Setup(p => p.Manifest).Returns(manifest);
mock.Setup(p => p.DirectoryPath).Returns(modFolder);
@@ -265,7 +265,7 @@ namespace SMAPI.Tests.Core
public void ProcessDependencies_Skips_Failed()
{
// arrange
- Mock<IModMetadata> mock = new Mock<IModMetadata>(MockBehavior.Strict);
+ Mock<IModMetadata> mock = new(MockBehavior.Strict);
mock.Setup(p => p.Status).Returns(ModMetadataStatus.Failed);
// act
@@ -380,7 +380,7 @@ namespace SMAPI.Tests.Core
Mock<IModMetadata> modA = this.GetMetadata("Mod A");
Mock<IModMetadata> modB = this.GetMetadata("Mod B", dependencies: new[] { "Mod A" });
Mock<IModMetadata> modC = this.GetMetadata("Mod C", dependencies: new[] { "Mod B" }, allowStatusChange: true);
- Mock<IModMetadata> modD = new Mock<IModMetadata>(MockBehavior.Strict);
+ Mock<IModMetadata> modD = new(MockBehavior.Strict);
modD.Setup(p => p.Manifest).Returns<IManifest>(null);
modD.Setup(p => p.Status).Returns(ModMetadataStatus.Failed);
@@ -507,7 +507,7 @@ namespace SMAPI.Tests.Core
/// <param name="allowStatusChange">Whether the code being tested is allowed to change the mod status.</param>
private Mock<IModMetadata> GetMetadata(string uniqueID, string[] dependencies, bool allowStatusChange = false)
{
- IManifest manifest = this.GetManifest(id: uniqueID, version: "1.0", dependencies: dependencies?.Select(dependencyID => (IManifestDependency)new ManifestDependency(dependencyID, null as ISemanticVersion)).ToArray());
+ IManifest manifest = this.GetManifest(id: uniqueID, version: "1.0", dependencies: dependencies.Select(dependencyID => (IManifestDependency)new ManifestDependency(dependencyID, null as ISemanticVersion)).ToArray());
return this.GetMetadata(manifest, allowStatusChange);
}
@@ -516,8 +516,8 @@ namespace SMAPI.Tests.Core
/// <param name="allowStatusChange">Whether the code being tested is allowed to change the mod status.</param>
private Mock<IModMetadata> GetMetadata(IManifest manifest, bool allowStatusChange = false)
{
- Mock<IModMetadata> mod = new Mock<IModMetadata>(MockBehavior.Strict);
- mod.Setup(p => p.DataRecord).Returns(() => null);
+ Mock<IModMetadata> mod = new(MockBehavior.Strict);
+ mod.Setup(p => p.DataRecord).Returns(this.GetModDataRecordVersionedFields());
mod.Setup(p => p.Status).Returns(ModMetadataStatus.Found);
mod.Setup(p => p.DisplayName).Returns(manifest.UniqueID);
mod.Setup(p => p.Manifest).Returns(manifest);
@@ -538,11 +538,22 @@ namespace SMAPI.Tests.Core
private void SetupMetadataForValidation(Mock<IModMetadata> mod, ModDataRecordVersionedFields? modRecord = null)
{
mod.Setup(p => p.Status).Returns(ModMetadataStatus.Found);
- mod.Setup(p => p.DataRecord).Returns(() => null);
mod.Setup(p => p.Manifest).Returns(this.GetManifest());
mod.Setup(p => p.DirectoryPath).Returns(Path.GetTempPath());
- mod.Setup(p => p.DataRecord).Returns(modRecord);
+ mod.Setup(p => p.DataRecord).Returns(modRecord ?? this.GetModDataRecordVersionedFields());
mod.Setup(p => p.GetUpdateKeys(It.IsAny<bool>())).Returns(Enumerable.Empty<UpdateKey>());
}
+
+ /// <summary>Generate a default mod data record.</summary>
+ private ModDataRecord GetModDataRecord()
+ {
+ return new("Default Display Name", new ModDataModel("Sample ID", null, ModWarning.None));
+ }
+
+ /// <summary>Generate a default mod data versioned fields instance.</summary>
+ private ModDataRecordVersionedFields GetModDataRecordVersionedFields()
+ {
+ return new ModDataRecordVersionedFields(this.GetModDataRecord());
+ }
}
}
diff --git a/src/SMAPI.Tests/Core/TranslationTests.cs b/src/SMAPI.Tests/Core/TranslationTests.cs
index f8f0e315..a52df607 100644
--- a/src/SMAPI.Tests/Core/TranslationTests.cs
+++ b/src/SMAPI.Tests/Core/TranslationTests.cs
@@ -1,11 +1,14 @@
-#nullable disable
-
using System;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.IO;
using System.Linq;
using NUnit.Framework;
using StardewModdingAPI;
+using StardewModdingAPI.Framework;
using StardewModdingAPI.Framework.ModHelpers;
+using StardewModdingAPI.Framework.ModLoading;
+using StardewModdingAPI.Toolkit.Serialization.Models;
using StardewValley;
namespace SMAPI.Tests.Core
@@ -18,7 +21,7 @@ namespace SMAPI.Tests.Core
** Data
*********/
/// <summary>Sample translation text for unit tests.</summary>
- public static string[] Samples = { null, "", " ", "boop", " boop " };
+ public static string?[] Samples = { null, "", " ", "boop", " boop " };
/*********
@@ -34,15 +37,15 @@ namespace SMAPI.Tests.Core
var data = new Dictionary<string, IDictionary<string, string>>();
// act
- ITranslationHelper helper = new TranslationHelper("ModID", "en", LocalizedContentManager.LanguageCode.en).SetTranslations(data);
+ ITranslationHelper helper = new TranslationHelper(this.CreateModMetadata(), "en", LocalizedContentManager.LanguageCode.en).SetTranslations(data);
Translation translation = helper.Get("key");
- Translation[] translationList = helper.GetTranslations()?.ToArray();
+ Translation[]? translationList = helper.GetTranslations()?.ToArray();
// assert
Assert.AreEqual("en", helper.Locale, "The locale doesn't match the input value.");
Assert.AreEqual(LocalizedContentManager.LanguageCode.en, helper.LocaleEnum, "The locale enum doesn't match the input value.");
Assert.IsNotNull(translationList, "The full list of translations is unexpectedly null.");
- Assert.AreEqual(0, translationList.Length, "The full list of translations is unexpectedly not empty.");
+ Assert.AreEqual(0, translationList!.Length, "The full list of translations is unexpectedly not empty.");
Assert.IsNotNull(translation, "The translation helper unexpectedly returned a null translation.");
Assert.AreEqual(this.GetPlaceholderText("key"), translation.ToString(), "The translation returned an unexpected value.");
@@ -56,8 +59,8 @@ namespace SMAPI.Tests.Core
var expected = this.GetExpectedTranslations();
// act
- var actual = new Dictionary<string, Translation[]>();
- TranslationHelper helper = new TranslationHelper("ModID", "en", LocalizedContentManager.LanguageCode.en).SetTranslations(data);
+ var actual = new Dictionary<string, Translation[]?>();
+ TranslationHelper helper = new TranslationHelper(this.CreateModMetadata(), "en", LocalizedContentManager.LanguageCode.en).SetTranslations(data);
foreach (string locale in expected.Keys)
{
this.AssertSetLocale(helper, locale, LocalizedContentManager.LanguageCode.en);
@@ -81,7 +84,7 @@ namespace SMAPI.Tests.Core
// act
var actual = new Dictionary<string, Translation[]>();
- TranslationHelper helper = new TranslationHelper("ModID", "en", LocalizedContentManager.LanguageCode.en).SetTranslations(data);
+ TranslationHelper helper = new TranslationHelper(this.CreateModMetadata(), "en", LocalizedContentManager.LanguageCode.en).SetTranslations(data);
foreach (string locale in expected.Keys)
{
this.AssertSetLocale(helper, locale, LocalizedContentManager.LanguageCode.en);
@@ -109,13 +112,13 @@ namespace SMAPI.Tests.Core
[TestCase(" ", ExpectedResult = true)]
[TestCase("boop", ExpectedResult = true)]
[TestCase(" boop ", ExpectedResult = true)]
- public bool Translation_HasValue(string text)
+ public bool Translation_HasValue(string? text)
{
return new Translation("pt-BR", "key", text).HasValue();
}
[Test(Description = "Assert that the translation's ToString method returns the expected text for various inputs.")]
- public void Translation_ToString([ValueSource(nameof(TranslationTests.Samples))] string text)
+ public void Translation_ToString([ValueSource(nameof(TranslationTests.Samples))] string? text)
{
// act
Translation translation = new("pt-BR", "key", text);
@@ -128,20 +131,20 @@ namespace SMAPI.Tests.Core
}
[Test(Description = "Assert that the translation's implicit string conversion returns the expected text for various inputs.")]
- public void Translation_ImplicitStringConversion([ValueSource(nameof(TranslationTests.Samples))] string text)
+ public void Translation_ImplicitStringConversion([ValueSource(nameof(TranslationTests.Samples))] string? text)
{
// act
Translation translation = new("pt-BR", "key", text);
// assert
if (translation.HasValue())
- Assert.AreEqual(text, (string)translation, "The translation returned an unexpected value given a valid input.");
+ Assert.AreEqual(text, (string?)translation, "The translation returned an unexpected value given a valid input.");
else
- Assert.AreEqual(this.GetPlaceholderText("key"), (string)translation, "The translation returned an unexpected value given a null or empty input.");
+ Assert.AreEqual(this.GetPlaceholderText("key"), (string?)translation, "The translation returned an unexpected value given a null or empty input.");
}
[Test(Description = "Assert that the translation returns the expected text given a use-placeholder setting.")]
- public void Translation_UsePlaceholder([Values(true, false)] bool value, [ValueSource(nameof(TranslationTests.Samples))] string text)
+ public void Translation_UsePlaceholder([Values(true, false)] bool value, [ValueSource(nameof(TranslationTests.Samples))] string? text)
{
// act
Translation translation = new Translation("pt-BR", "key", text).UsePlaceholder(value);
@@ -156,7 +159,7 @@ namespace SMAPI.Tests.Core
}
[Test(Description = "Assert that the translation returns the expected text after setting the default.")]
- public void Translation_Default([ValueSource(nameof(TranslationTests.Samples))] string text, [ValueSource(nameof(TranslationTests.Samples))] string @default)
+ public void Translation_Default([ValueSource(nameof(TranslationTests.Samples))] string? text, [ValueSource(nameof(TranslationTests.Samples))] string? @default)
{
// act
Translation translation = new Translation("pt-BR", "key", text).Default(@default);
@@ -192,7 +195,7 @@ namespace SMAPI.Tests.Core
break;
case "class":
- translation = translation.Tokens(new TokenModel { Start = start, Middle = middle, End = end });
+ translation = translation.Tokens(new TokenModel(start, middle, end));
break;
case "IDictionary<string, object>":
@@ -326,21 +329,63 @@ namespace SMAPI.Tests.Core
return string.Format(Translation.PlaceholderText, key);
}
+ /// <summary>Create a fake mod manifest.</summary>
+ private IModMetadata CreateModMetadata()
+ {
+ string id = $"smapi.unit-tests.fake-mod-{Guid.NewGuid():N}";
+
+ string tempPath = Path.Combine(Path.GetTempPath(), id);
+ return new ModMetadata(
+ displayName: "Mod Display Name",
+ directoryPath: tempPath,
+ rootPath: tempPath,
+ manifest: new Manifest(
+ uniqueID: id,
+ name: "Mod Name",
+ author: "Mod Author",
+ description: "Mod Description",
+ version: new SemanticVersion(1, 0, 0)
+ ),
+ dataRecord: null,
+ isIgnored: false
+ );
+ }
+
/*********
** Test models
*********/
/// <summary>A model used to test token support.</summary>
+ [SuppressMessage("ReSharper", "NotAccessedField.Local", Justification = "Used dynamically via translation helper.")]
+ [SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local", Justification = "Used dynamically via translation helper.")]
private class TokenModel
{
+ /*********
+ ** Accessors
+ *********/
/// <summary>A sample token property.</summary>
- public string Start { get; set; }
+ public string Start { get; }
/// <summary>A sample token property.</summary>
- public string Middle { get; set; }
+ public string Middle { get; }
/// <summary>A sample token field.</summary>
public string End;
+
+
+ /*********
+ ** public methods
+ *********/
+ /// <summary>Construct an instance.</summary>
+ /// <param name="start">A sample token property.</param>
+ /// <param name="middle">A sample token field.</param>
+ /// <param name="end">A sample token property.</param>
+ public TokenModel(string start, string middle, string end)
+ {
+ this.Start = start;
+ this.Middle = middle;
+ this.End = end;
+ }
}
}
}
diff --git a/src/SMAPI.Tests/Sample.cs b/src/SMAPI.Tests/Sample.cs
index 6d4339ca..9587a100 100644
--- a/src/SMAPI.Tests/Sample.cs
+++ b/src/SMAPI.Tests/Sample.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
using System;
namespace SMAPI.Tests
diff --git a/src/SMAPI.Tests/Utilities/KeybindListTests.cs b/src/SMAPI.Tests/Utilities/KeybindListTests.cs
index f5c156c4..c4c086de 100644
--- a/src/SMAPI.Tests/Utilities/KeybindListTests.cs
+++ b/src/SMAPI.Tests/Utilities/KeybindListTests.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
using System;
using System.Collections.Generic;
using NUnit.Framework;
@@ -23,12 +21,12 @@ namespace SMAPI.Tests.Utilities
public void TryParse_SimpleValue(SButton button)
{
// act
- bool success = KeybindList.TryParse($"{button}", out KeybindList parsed, out string[] errors);
+ bool success = KeybindList.TryParse($"{button}", out KeybindList? parsed, out string[] errors);
// assert
Assert.IsTrue(success, "Parsing unexpectedly failed.");
Assert.IsNotNull(parsed, "The parsed result should not be null.");
- Assert.AreEqual(parsed.ToString(), $"{button}");
+ Assert.AreEqual(parsed!.ToString(), $"{button}");
Assert.IsNotNull(errors, message: "The errors should never be null.");
Assert.IsEmpty(errors, message: "The input bindings incorrectly reported errors.");
}
@@ -46,17 +44,17 @@ namespace SMAPI.Tests.Utilities
[TestCase(",", ExpectedResult = "None")]
[TestCase("A,", ExpectedResult = "A")]
[TestCase(",A", ExpectedResult = "A")]
- public string TryParse_MultiValues(string input)
+ public string TryParse_MultiValues(string? input)
{
// act
- bool success = KeybindList.TryParse(input, out KeybindList parsed, out string[] errors);
+ bool success = KeybindList.TryParse(input, out KeybindList? parsed, out string[] errors);
// assert
Assert.IsTrue(success, "Parsing unexpectedly failed.");
Assert.IsNotNull(parsed, "The parsed result should not be null.");
Assert.IsNotNull(errors, message: "The errors should never be null.");
Assert.IsEmpty(errors, message: "The input bindings incorrectly reported errors.");
- return parsed.ToString();
+ return parsed!.ToString();
}
/// <summary>Assert invalid values are rejected.</summary>
@@ -69,7 +67,7 @@ namespace SMAPI.Tests.Utilities
public void TryParse_InvalidValues(string input, string expectedError)
{
// act
- bool success = KeybindList.TryParse(input, out KeybindList parsed, out string[] errors);
+ bool success = KeybindList.TryParse(input, out KeybindList? parsed, out string[] errors);
// assert
Assert.IsFalse(success, "Parsing unexpectedly succeeded.");
@@ -100,13 +98,15 @@ namespace SMAPI.Tests.Utilities
public SButtonState GetState(string input, string stateMap)
{
// act
- bool success = KeybindList.TryParse(input, out KeybindList parsed, out string[] errors);
+ bool success = KeybindList.TryParse(input, out KeybindList? parsed, out string[] errors);
if (success && parsed?.Keybinds != null)
{
- foreach (var keybind in parsed.Keybinds)
+ foreach (Keybind? keybind in parsed.Keybinds)
+ {
#pragma warning disable 618 // method is marked obsolete because it should only be used in unit tests
keybind.GetButtonState = key => this.GetStateFromMap(key, stateMap);
#pragma warning restore 618
+ }
}
// assert
@@ -114,7 +114,7 @@ namespace SMAPI.Tests.Utilities
Assert.IsNotNull(parsed, "The parsed result should not be null.");
Assert.IsNotNull(errors, message: "The errors should never be null.");
Assert.IsEmpty(errors, message: "The input bindings incorrectly reported errors.");
- return parsed.GetState();
+ return parsed!.GetState();
}
diff --git a/src/SMAPI.Tests/Utilities/PathUtilitiesTests.cs b/src/SMAPI.Tests/Utilities/PathUtilitiesTests.cs
index ae2cc6ce..3219d89d 100644
--- a/src/SMAPI.Tests/Utilities/PathUtilitiesTests.cs
+++ b/src/SMAPI.Tests/Utilities/PathUtilitiesTests.cs
@@ -1,5 +1,4 @@
-#nullable disable
-
+using System.Diagnostics.CodeAnalysis;
using System.IO;
using NUnit.Framework;
using StardewModdingAPI.Toolkit.Utilities;
@@ -8,6 +7,7 @@ namespace SMAPI.Tests.Utilities
{
/// <summary>Unit tests for <see cref="PathUtilities"/>.</summary>
[TestFixture]
+ [SuppressMessage("ReSharper", "StringLiteralTypo", Justification = "These are standard game install paths.")]
internal class PathUtilitiesTests
{
/*********
@@ -16,136 +16,125 @@ namespace SMAPI.Tests.Utilities
/// <summary>Sample paths used in unit tests.</summary>
public static readonly SamplePath[] SamplePaths = {
// Windows absolute path
- new()
- {
- OriginalPath = @"C:\Program Files (x86)\Steam\steamapps\common\Stardew Valley",
+ new(
+ OriginalPath: @"C:\Program Files (x86)\Steam\steamapps\common\Stardew Valley",
- Segments = new[] { "C:", "Program Files (x86)", "Steam", "steamapps", "common", "Stardew Valley" },
- SegmentsLimit3 = new [] { "C:", "Program Files (x86)", @"Steam\steamapps\common\Stardew Valley" },
+ Segments: new[] { "C:", "Program Files (x86)", "Steam", "steamapps", "common", "Stardew Valley" },
+ SegmentsLimit3: new [] { "C:", "Program Files (x86)", @"Steam\steamapps\common\Stardew Valley" },
- NormalizedOnWindows = @"C:\Program Files (x86)\Steam\steamapps\common\Stardew Valley",
- NormalizedOnUnix = @"C:/Program Files (x86)/Steam/steamapps/common/Stardew Valley"
- },
+ NormalizedOnWindows: @"C:\Program Files (x86)\Steam\steamapps\common\Stardew Valley",
+ NormalizedOnUnix: @"C:/Program Files (x86)/Steam/steamapps/common/Stardew Valley"
+ ),
// Windows absolute path (with trailing slash)
- new()
- {
- OriginalPath = @"C:\Program Files (x86)\Steam\steamapps\common\Stardew Valley\",
+ new(
+ OriginalPath: @"C:\Program Files (x86)\Steam\steamapps\common\Stardew Valley\",
- Segments = new[] { "C:", "Program Files (x86)", "Steam", "steamapps", "common", "Stardew Valley" },
- SegmentsLimit3 = new [] { "C:", "Program Files (x86)", @"Steam\steamapps\common\Stardew Valley\" },
+ Segments: new[] { "C:", "Program Files (x86)", "Steam", "steamapps", "common", "Stardew Valley" },
+ SegmentsLimit3: new [] { "C:", "Program Files (x86)", @"Steam\steamapps\common\Stardew Valley\" },
- NormalizedOnWindows = @"C:\Program Files (x86)\Steam\steamapps\common\Stardew Valley\",
- NormalizedOnUnix = @"C:/Program Files (x86)/Steam/steamapps/common/Stardew Valley/"
- },
+ NormalizedOnWindows: @"C:\Program Files (x86)\Steam\steamapps\common\Stardew Valley\",
+ NormalizedOnUnix: @"C:/Program Files (x86)/Steam/steamapps/common/Stardew Valley/"
+ ),
// Windows relative path
- new()
- {
- OriginalPath = @"Content\Characters\Dialogue\Abigail",
+ new(
+ OriginalPath: @"Content\Characters\Dialogue\Abigail",
- Segments = new [] { "Content", "Characters", "Dialogue", "Abigail" },
- SegmentsLimit3 = new [] { "Content", "Characters", @"Dialogue\Abigail" },
+ Segments: new [] { "Content", "Characters", "Dialogue", "Abigail" },
+ SegmentsLimit3: new [] { "Content", "Characters", @"Dialogue\Abigail" },
- NormalizedOnWindows = @"Content\Characters\Dialogue\Abigail",
- NormalizedOnUnix = @"Content/Characters/Dialogue/Abigail"
- },
+ NormalizedOnWindows: @"Content\Characters\Dialogue\Abigail",
+ NormalizedOnUnix: @"Content/Characters/Dialogue/Abigail"
+ ),
// Windows relative path (with directory climbing)
- new()
- {
- OriginalPath = @"..\..\Content",
+ new(
+ OriginalPath: @"..\..\Content",
- Segments = new [] { "..", "..", "Content" },
- SegmentsLimit3 = new [] { "..", "..", "Content" },
+ Segments: new [] { "..", "..", "Content" },
+ SegmentsLimit3: new [] { "..", "..", "Content" },
- NormalizedOnWindows = @"..\..\Content",
- NormalizedOnUnix = @"../../Content"
- },
+ NormalizedOnWindows: @"..\..\Content",
+ NormalizedOnUnix: @"../../Content"
+ ),
// Windows UNC path
- new()
- {
- OriginalPath = @"\\unc\path",
+ new(
+ OriginalPath: @"\\unc\path",
- Segments = new [] { "unc", "path" },
- SegmentsLimit3 = new [] { "unc", "path" },
+ Segments: new [] { "unc", "path" },
+ SegmentsLimit3: new [] { "unc", "path" },
- NormalizedOnWindows = @"\\unc\path",
- NormalizedOnUnix = "/unc/path" // there's no good way to normalize this on Unix since UNC paths aren't supported; path normalization is meant for asset names anyway, so this test only ensures it returns some sort of sane value
- },
+ NormalizedOnWindows: @"\\unc\path",
+ NormalizedOnUnix: "/unc/path" // there's no good way to normalize this on Unix since UNC paths aren't supported; path normalization is meant for asset names anyway, so this test only ensures it returns some sort of sane value
+ ),
// Linux absolute path
- new()
- {
- OriginalPath = @"/home/.steam/steam/steamapps/common/Stardew Valley",
+ new(
+ OriginalPath: @"/home/.steam/steam/steamapps/common/Stardew Valley",
- Segments = new [] { "home", ".steam", "steam", "steamapps", "common", "Stardew Valley" },
- SegmentsLimit3 = new [] { "home", ".steam", "steam/steamapps/common/Stardew Valley" },
+ Segments: new [] { "home", ".steam", "steam", "steamapps", "common", "Stardew Valley" },
+ SegmentsLimit3: new [] { "home", ".steam", "steam/steamapps/common/Stardew Valley" },
- NormalizedOnWindows = @"\home\.steam\steam\steamapps\common\Stardew Valley",
- NormalizedOnUnix = @"/home/.steam/steam/steamapps/common/Stardew Valley"
- },
+ NormalizedOnWindows: @"\home\.steam\steam\steamapps\common\Stardew Valley",
+ NormalizedOnUnix: @"/home/.steam/steam/steamapps/common/Stardew Valley"
+ ),
// Linux absolute path (with trailing slash)
- new()
- {
- OriginalPath = @"/home/.steam/steam/steamapps/common/Stardew Valley/",
+ new(
+ OriginalPath: @"/home/.steam/steam/steamapps/common/Stardew Valley/",
- Segments = new [] { "home", ".steam", "steam", "steamapps", "common", "Stardew Valley" },
- SegmentsLimit3 = new [] { "home", ".steam", "steam/steamapps/common/Stardew Valley/" },
+ Segments: new [] { "home", ".steam", "steam", "steamapps", "common", "Stardew Valley" },
+ SegmentsLimit3: new [] { "home", ".steam", "steam/steamapps/common/Stardew Valley/" },
- NormalizedOnWindows = @"\home\.steam\steam\steamapps\common\Stardew Valley\",
- NormalizedOnUnix = @"/home/.steam/steam/steamapps/common/Stardew Valley/"
- },
+ NormalizedOnWindows: @"\home\.steam\steam\steamapps\common\Stardew Valley\",
+ NormalizedOnUnix: @"/home/.steam/steam/steamapps/common/Stardew Valley/"
+ ),
// Linux absolute path (with ~)
- new()
- {
- OriginalPath = @"~/.steam/steam/steamapps/common/Stardew Valley",
+ new(
+ OriginalPath: @"~/.steam/steam/steamapps/common/Stardew Valley",
- Segments = new [] { "~", ".steam", "steam", "steamapps", "common", "Stardew Valley" },
- SegmentsLimit3 = new [] { "~", ".steam", "steam/steamapps/common/Stardew Valley" },
+ Segments: new [] { "~", ".steam", "steam", "steamapps", "common", "Stardew Valley" },
+ SegmentsLimit3: new [] { "~", ".steam", "steam/steamapps/common/Stardew Valley" },
- NormalizedOnWindows = @"~\.steam\steam\steamapps\common\Stardew Valley",
- NormalizedOnUnix = @"~/.steam/steam/steamapps/common/Stardew Valley"
- },
+ NormalizedOnWindows: @"~\.steam\steam\steamapps\common\Stardew Valley",
+ NormalizedOnUnix: @"~/.steam/steam/steamapps/common/Stardew Valley"
+ ),
// Linux relative path
- new()
- {
- OriginalPath = @"Content/Characters/Dialogue/Abigail",
+ new(
+ OriginalPath: @"Content/Characters/Dialogue/Abigail",
- Segments = new [] { "Content", "Characters", "Dialogue", "Abigail" },
- SegmentsLimit3 = new [] { "Content", "Characters", "Dialogue/Abigail" },
+ Segments: new [] { "Content", "Characters", "Dialogue", "Abigail" },
+ SegmentsLimit3: new [] { "Content", "Characters", "Dialogue/Abigail" },
- NormalizedOnWindows = @"Content\Characters\Dialogue\Abigail",
- NormalizedOnUnix = @"Content/Characters/Dialogue/Abigail"
- },
+ NormalizedOnWindows: @"Content\Characters\Dialogue\Abigail",
+ NormalizedOnUnix: @"Content/Characters/Dialogue/Abigail"
+ ),
// Linux relative path (with directory climbing)
- new()
- {
- OriginalPath = @"../../Content",
+ new(
+ OriginalPath: @"../../Content",
- Segments = new [] { "..", "..", "Content" },
- SegmentsLimit3 = new [] { "..", "..", "Content" },
+ Segments: new [] { "..", "..", "Content" },
+ SegmentsLimit3: new [] { "..", "..", "Content" },
- NormalizedOnWindows = @"..\..\Content",
- NormalizedOnUnix = @"../../Content"
- },
+ NormalizedOnWindows: @"..\..\Content",
+ NormalizedOnUnix: @"../../Content"
+ ),
// Mixed directory separators
- new()
- {
- OriginalPath = @"C:\some/mixed\path/separators",
+ new(
+ OriginalPath: @"C:\some/mixed\path/separators",
- Segments = new [] { "C:", "some", "mixed", "path", "separators" },
- SegmentsLimit3 = new [] { "C:", "some", @"mixed\path/separators" },
+ Segments: new [] { "C:", "some", "mixed", "path", "separators" },
+ SegmentsLimit3: new [] { "C:", "some", @"mixed\path/separators" },
- NormalizedOnWindows = @"C:\some\mixed\path\separators",
- NormalizedOnUnix = @"C:/some/mixed/path/separators"
- },
+ NormalizedOnWindows: @"C:\some\mixed\path\separators",
+ NormalizedOnUnix: @"C:/some/mixed/path/separators"
+ )
};
@@ -283,14 +272,14 @@ namespace SMAPI.Tests.Utilities
/*********
** Private classes
*********/
- public class SamplePath
+ /// <summary>A sample path in multiple formats.</summary>
+ /// <param name="OriginalPath">The original path to pass to the <see cref="PathUtilities"/>.</param>
+ /// <param name="Segments">The normalized path segments.</param>
+ /// <param name="SegmentsLimit3">The normalized path segments, if we stop segmenting after the second one.</param>
+ /// <param name="NormalizedOnWindows">The normalized form on Windows.</param>
+ /// <param name="NormalizedOnUnix">The normalized form on Linux or macOS.</param>
+ public record SamplePath(string OriginalPath, string[] Segments, string[] SegmentsLimit3, string NormalizedOnWindows, string NormalizedOnUnix)
{
- public string OriginalPath { get; set; }
- public string[] Segments { get; set; }
- public string[] SegmentsLimit3 { get; set; }
- public string NormalizedOnWindows { get; set; }
- public string NormalizedOnUnix { get; set; }
-
public override string ToString()
{
return this.OriginalPath;
diff --git a/src/SMAPI.Tests/Utilities/SDateTests.cs b/src/SMAPI.Tests/Utilities/SDateTests.cs
index a4a36828..b9c3d202 100644
--- a/src/SMAPI.Tests/Utilities/SDateTests.cs
+++ b/src/SMAPI.Tests/Utilities/SDateTests.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
@@ -258,7 +256,7 @@ namespace SMAPI.Tests.Utilities
{
SDate date = new(day, season, year);
int hash = date.GetHashCode();
- if (hashes.TryGetValue(hash, out SDate otherDate))
+ if (hashes.TryGetValue(hash, out SDate? otherDate))
Assert.Fail($"Received identical hash code {hash} for dates {otherDate} and {date}.");
if (hash < lastHash)
Assert.Fail($"Received smaller hash code for date {date} ({hash}) relative to {hashes[lastHash]} ({lastHash}).");
@@ -298,7 +296,7 @@ namespace SMAPI.Tests.Utilities
[TestCase(Dates.Now, Dates.NextDay, ExpectedResult = false)]
[TestCase(Dates.Now, Dates.NextMonth, ExpectedResult = false)]
[TestCase(Dates.Now, Dates.NextYear, ExpectedResult = false)]
- public bool Operators_Equals(string now, string other)
+ public bool Operators_Equals(string? now, string other)
{
return this.GetDate(now) == this.GetDate(other);
}
@@ -312,7 +310,7 @@ namespace SMAPI.Tests.Utilities
[TestCase(Dates.Now, Dates.NextDay, ExpectedResult = true)]
[TestCase(Dates.Now, Dates.NextMonth, ExpectedResult = true)]
[TestCase(Dates.Now, Dates.NextYear, ExpectedResult = true)]
- public bool Operators_NotEquals(string now, string other)
+ public bool Operators_NotEquals(string? now, string other)
{
return this.GetDate(now) != this.GetDate(other);
}
@@ -326,7 +324,7 @@ namespace SMAPI.Tests.Utilities
[TestCase(Dates.Now, Dates.NextDay, ExpectedResult = true)]
[TestCase(Dates.Now, Dates.NextMonth, ExpectedResult = true)]
[TestCase(Dates.Now, Dates.NextYear, ExpectedResult = true)]
- public bool Operators_LessThan(string now, string other)
+ public bool Operators_LessThan(string? now, string other)
{
return this.GetDate(now) < this.GetDate(other);
}
@@ -340,7 +338,7 @@ namespace SMAPI.Tests.Utilities
[TestCase(Dates.Now, Dates.NextDay, ExpectedResult = true)]
[TestCase(Dates.Now, Dates.NextMonth, ExpectedResult = true)]
[TestCase(Dates.Now, Dates.NextYear, ExpectedResult = true)]
- public bool Operators_LessThanOrEqual(string now, string other)
+ public bool Operators_LessThanOrEqual(string? now, string other)
{
return this.GetDate(now) <= this.GetDate(other);
}
@@ -354,7 +352,7 @@ namespace SMAPI.Tests.Utilities
[TestCase(Dates.Now, Dates.NextDay, ExpectedResult = false)]
[TestCase(Dates.Now, Dates.NextMonth, ExpectedResult = false)]
[TestCase(Dates.Now, Dates.NextYear, ExpectedResult = false)]
- public bool Operators_MoreThan(string now, string other)
+ public bool Operators_MoreThan(string? now, string other)
{
return this.GetDate(now) > this.GetDate(other);
}
@@ -368,7 +366,7 @@ namespace SMAPI.Tests.Utilities
[TestCase(Dates.Now, Dates.NextDay, ExpectedResult = false)]
[TestCase(Dates.Now, Dates.NextMonth, ExpectedResult = false)]
[TestCase(Dates.Now, Dates.NextYear, ExpectedResult = false)]
- public bool Operators_MoreThanOrEqual(string now, string other)
+ public bool Operators_MoreThanOrEqual(string? now, string other)
{
return this.GetDate(now) > this.GetDate(other);
}
@@ -379,7 +377,8 @@ namespace SMAPI.Tests.Utilities
*********/
/// <summary>Convert a string date into a game date, to make unit tests easier to read.</summary>
/// <param name="dateStr">The date string like "dd MMMM yy".</param>
- private SDate GetDate(string dateStr)
+ [return: NotNullIfNotNull("dateStr")]
+ private SDate? GetDate(string? dateStr)
{
if (dateStr == null)
return null;
diff --git a/src/SMAPI.Tests/WikiClient/ChangeDescriptorTests.cs b/src/SMAPI.Tests/WikiClient/ChangeDescriptorTests.cs
index 7695fbf8..8e7e1fb8 100644
--- a/src/SMAPI.Tests/WikiClient/ChangeDescriptorTests.cs
+++ b/src/SMAPI.Tests/WikiClient/ChangeDescriptorTests.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
using System.Collections.Generic;
using NUnit.Framework;
using StardewModdingAPI;
@@ -22,15 +20,14 @@ namespace SMAPI.Tests.WikiClient
{
// arrange
string rawDescriptor = "-Nexus:2400, -B, XX → YY, Nexus:451,+A, XXX → YYY, invalidA →, → invalidB";
- string[] expectedAdd = new[] { "Nexus:451", "A" };
- string[] expectedRemove = new[] { "Nexus:2400", "B" };
+ string[] expectedAdd = { "Nexus:451", "A" };
+ string[] expectedRemove = { "Nexus:2400", "B" };
IDictionary<string, string> expectedReplace = new Dictionary<string, string>
{
["XX"] = "YY",
["XXX"] = "YYY"
};
- string[] expectedErrors = new[]
- {
+ string[] expectedErrors = {
"Failed parsing ' invalidA →': can't map to a blank value. Use the '-value' format to remove a value.",
"Failed parsing ' → invalidB': can't map from a blank old value. Use the '+value' format to add a value."
};
@@ -50,15 +47,14 @@ namespace SMAPI.Tests.WikiClient
{
// arrange
string rawDescriptor = "-1.0.1, -2.0-beta, 1.00 → 1.0, 1.0.0,+2.0-beta.15, 2.0 → 2.0-beta, invalidA →, → invalidB";
- string[] expectedAdd = new[] { "1.0.0", "2.0.0-beta.15" };
- string[] expectedRemove = new[] { "1.0.1", "2.0.0-beta" };
+ string[] expectedAdd = { "1.0.0", "2.0.0-beta.15" };
+ string[] expectedRemove = { "1.0.1", "2.0.0-beta" };
IDictionary<string, string> expectedReplace = new Dictionary<string, string>
{
["1.00"] = "1.0.0",
["2.0.0"] = "2.0.0-beta"
};
- string[] expectedErrors = new[]
- {
+ string[] expectedErrors = {
"Failed parsing ' invalidA →': can't map to a blank value. Use the '-value' format to remove a value.",
"Failed parsing ' → invalidB': can't map from a blank old value. Use the '+value' format to add a value."
};
@@ -67,7 +63,7 @@ namespace SMAPI.Tests.WikiClient
ChangeDescriptor parsed = ChangeDescriptor.Parse(
rawDescriptor,
out string[] errors,
- formatValue: raw => SemanticVersion.TryParse(raw, out ISemanticVersion version)
+ formatValue: raw => SemanticVersion.TryParse(raw, out ISemanticVersion? version)
? version.ToString()
: raw
);
@@ -111,9 +107,9 @@ namespace SMAPI.Tests.WikiClient
[TestCase("", "+Nexus:A, Nexus:B, -Chucklefish:14, Nexus:2400 → Nexus:2401, Nexus:A→Nexus:B", ExpectedResult = "Nexus:A, Nexus:B")]
[TestCase("Nexus:2400", "+Nexus:A, Nexus:B, -Chucklefish:14, Nexus:2400 → Nexus:2401, Nexus:A→Nexus:B", ExpectedResult = "Nexus:2401, Nexus:A, Nexus:B")]
[TestCase("Nexus:2400, Nexus:2401, Nexus:B,Chucklefish:14", "+Nexus:A, Nexus:B, -Chucklefish:14, Nexus:2400 → Nexus:2401, Nexus:A→Nexus:B", ExpectedResult = "Nexus:2401, Nexus:2401, Nexus:B, Nexus:A")]
- public string Apply_Raw(string input, string descriptor)
+ public string Apply_Raw(string input, string? descriptor)
{
- var parsed = ChangeDescriptor.Parse(descriptor, out string[] errors);
+ ChangeDescriptor parsed = ChangeDescriptor.Parse(descriptor, out string[] errors);
Assert.IsEmpty(errors, "Parsing the descriptor failed.");
@@ -128,7 +124,7 @@ namespace SMAPI.Tests.WikiClient
[TestCase("-Nexus:2400", ExpectedResult = "-Nexus:2400")]
[TestCase(" Nexus:2400 →Nexus:2401 ", ExpectedResult = "Nexus:2400 → Nexus:2401")]
[TestCase("+Nexus:A, Nexus:B, -Chucklefish:14, Nexus:2400 → Nexus:2401, Nexus:A→Nexus:B", ExpectedResult = "+Nexus:A, +Nexus:B, -Chucklefish:14, Nexus:2400 → Nexus:2401, Nexus:A → Nexus:B")]
- public string ToString(string descriptor)
+ public string ToString(string? descriptor)
{
var parsed = ChangeDescriptor.Parse(descriptor, out string[] errors);