diff options
37 files changed, 737 insertions, 199 deletions
diff --git a/build/common.targets b/build/common.targets index 5eb69901..11584d33 100644 --- a/build/common.targets +++ b/build/common.targets @@ -1,7 +1,7 @@ <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <PropertyGroup> <!--set general build properties --> - <Version>3.12.6</Version> + <Version>3.12.7</Version> <Product>SMAPI</Product> <LangVersion>latest</LangVersion> <AssemblySearchPaths>$(AssemblySearchPaths);{GAC}</AssemblySearchPaths> @@ -54,13 +54,13 @@ <Copy SourceFiles="@(TranslationFiles)" DestinationFolder="$(GamePath)\Mods\$(AssemblyName)\i18n" /> </Target> - <Target Name="CopyToolkit" Condition="'$(MSBuildProjectName)' == 'SMAPI.Toolkit' AND $(TargetFramework) == 'net4.5'" AfterTargets="PostBuildEvent"> + <Target Name="CopyToolkit" Condition="'$(MSBuildProjectName)' == 'SMAPI.Toolkit' AND $(TargetFramework) == 'net452'" AfterTargets="PostBuildEvent"> <Copy SourceFiles="$(TargetDir)\$(TargetName).dll" DestinationFolder="$(GamePath)\smapi-internal" /> <Copy SourceFiles="$(TargetDir)\$(TargetName).pdb" DestinationFolder="$(GamePath)\smapi-internal" /> <Copy SourceFiles="$(TargetDir)\$(TargetName).xml" DestinationFolder="$(GamePath)\smapi-internal" /> </Target> - <Target Name="CopyToolkitCoreInterfaces" Condition="'$(MSBuildProjectName)' == 'SMAPI.Toolkit.CoreInterfaces' AND $(TargetFramework) == 'net4.5'" AfterTargets="PostBuildEvent"> + <Target Name="CopyToolkitCoreInterfaces" Condition="'$(MSBuildProjectName)' == 'SMAPI.Toolkit.CoreInterfaces' AND $(TargetFramework) == 'net452'" AfterTargets="PostBuildEvent"> <Copy SourceFiles="$(TargetDir)\$(TargetName).dll" DestinationFolder="$(GamePath)\smapi-internal" /> <Copy SourceFiles="$(TargetDir)\$(TargetName).pdb" DestinationFolder="$(GamePath)\smapi-internal" /> <Copy SourceFiles="$(TargetDir)\$(TargetName).xml" DestinationFolder="$(GamePath)\smapi-internal" /> diff --git a/build/prepare-install-package.targets b/build/prepare-install-package.targets index 88e565f9..601f6496 100644 --- a/build/prepare-install-package.targets +++ b/build/prepare-install-package.targets @@ -14,7 +14,7 @@ <OutRootPath>$(SolutionDir)\..\bin</OutRootPath> <SmapiBin>$(BuildRootPath)\SMAPI\bin\$(Configuration)</SmapiBin> - <ToolkitBin>$(BuildRootPath)\SMAPI.Toolkit\bin\$(Configuration)\net4.5</ToolkitBin> + <ToolkitBin>$(BuildRootPath)\SMAPI.Toolkit\bin\$(Configuration)\net452</ToolkitBin> <ConsoleCommandsBin>$(BuildRootPath)\SMAPI.Mods.ConsoleCommands\bin\$(Configuration)</ConsoleCommandsBin> <ErrorHandlerBin>$(BuildRootPath)\SMAPI.Mods.ErrorHandler\bin\$(Configuration)</ErrorHandlerBin> <SaveBackupBin>$(BuildRootPath)\SMAPI.Mods.SaveBackup\bin\$(Configuration)</SaveBackupBin> diff --git a/docs/release-notes.md b/docs/release-notes.md index c51dec39..a7a5e6dd 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -3,6 +3,24 @@ # Release notes ## Upcoming release * For players: + * Fixed mod edits to the farmhouse shifting the player down one tile in some cases. + +* For mod authors: + * SMAPI now intercepts dictionary duplicate-key errors and adds the key to the error message to simplify troubleshooting. (Due to Harmony limitations, this only works for the dictionary types used by the game.) + * Fixed map tile rotations/flips not working for farmhands in split-screen mode. + * Fixed barn/coop exit warps being reset when you edit their interior map. + +* For the web UI: + * Added support for unified [mod data overrides](https://stardewvalleywiki.com/Modding:Mod_compatibility#Mod_data_overrides) defined on the wiki. + * The mod compatibility list now shows separate beta stats when 'show advanced info' is enabled. + +## 3.12.7 +Released 18 September 2021 for Stardew Valley 1.5.4. + +* For players: + * Added more progress updates in the log during startup. + * Simplified asset load error message. + * Simplified exception logs. * Fixed crash loading mods with corrupted translation files. * For mod authors: @@ -14,7 +32,7 @@ * Fixed JSON validator line numbers sometimes incorrect. ## 3.12.6 -Released 03 September 2021 for Stardew Valley 1.5.4 or later. +Released 03 September 2021 for Stardew Valley 1.5.4. * For players: * Added friendly error when using SMAPI 3.2._x_ with Stardew Valley 1.5.5 or later. @@ -38,18 +56,18 @@ to format asset names, you should switch to `PathUtilities.NormalizeAssetName` n continue working in the next game update. ## 3.12.5 -Released 26 August 2021 for Stardew Valley 1.5.4 or later. +Released 26 August 2021 for Stardew Valley 1.5.4. * Fixed some mods in unofficial 64-bit mode no longer loading after SMAPI 3.12.3. ## 3.12.4 -Released 25 August 2021 for Stardew Valley 1.5.4 or later. +Released 25 August 2021 for Stardew Valley 1.5.4. * For players: * Fixed error loading some mods in SMAPI 3.12.3. ## 3.12.3 -Released 25 August 2021 for Stardew Valley 1.5.4 or later. +Released 25 August 2021 for Stardew Valley 1.5.4. * For players: * Added friendly error in 64-bit mode when a mod is 32-bit only. @@ -68,7 +86,7 @@ Released 25 August 2021 for Stardew Valley 1.5.4 or later. * Fixed update checks shown for prerelease mod versions if you have a working non-prerelease version. ## 3.12.2 -Released 05 August 2021 for Stardew Valley 1.5.4 or later. +Released 05 August 2021 for Stardew Valley 1.5.4. * For players: * Fixed error creating a new save or joining a multiplayer world in 3.12.1. @@ -79,7 +97,7 @@ Released 05 August 2021 for Stardew Valley 1.5.4 or later. * Fixed `NullReferenceException` in SMAPI's error-handling when trying to handle an invalid `ReflectionTypeLoadException`. ## 3.12.1 -Released 03 August 2021 for Stardew Valley 1.5.4 or later. +Released 03 August 2021 for Stardew Valley 1.5.4. * For players: * The software conflict message is now shown as a warning to simplify troubleshooting. @@ -90,7 +108,7 @@ Released 03 August 2021 for Stardew Valley 1.5.4 or later. * Fixed `Constants.Save*` fields incorrect if the save's folder name and ID don't match. ## 3.12.0 -Released 01 August 2021 for Stardew Valley 1.5.4 or later. See [release highlights](https://www.patreon.com/posts/54388616). +Released 01 August 2021 for Stardew Valley 1.5.4. See [release highlights](https://www.patreon.com/posts/54388616). * For players: * Added save recovery when content mods leave null objects in the save (in _Error Handler_). @@ -108,7 +126,7 @@ Released 01 August 2021 for Stardew Valley 1.5.4 or later. See [release highligh * Fixed reloading a map not correctly reapplying interior doors. ## 3.11.0 -Released 09 July 2021 for Stardew Valley 1.5.4 or later. See [release highlights](https://www.patreon.com/posts/53514295). +Released 09 July 2021 for Stardew Valley 1.5.4. See [release highlights](https://www.patreon.com/posts/53514295). * For players: * Updated for Stardew Valley 1.4.5 multiplayer hotfix on Linux/macOS. @@ -132,13 +150,13 @@ Released 09 July 2021 for Stardew Valley 1.5.4 or later. See [release highlights * Fixed JSON schema for `i18n` files requiring the wrong value for the `$schema` field. ## 3.10.1 -Released 03 May 2021 for Stardew Valley 1.5.4 or later. +Released 03 May 2021 for Stardew Valley 1.5.4. * For players: * Fixed installer leaving an unneeded `StardewModdingAPI-x64.exe` file in 32-bit game folders. ## 3.10 -Released 03 May 2021 for Stardew Valley 1.5.4 or later. See [release highlights](https://www.patreon.com/posts/50764911). +Released 03 May 2021 for Stardew Valley 1.5.4. See [release highlights](https://www.patreon.com/posts/50764911). * For players: * Added full support for the [unofficial 64-bit Stardew Valley patch](https://stardewvalleywiki.com/Modding:Migrate_to_64-bit_on_Windows), which removes memory limits. The installer detects which version of SMAPI you need, and SMAPI shows update alerts for Stardew64Installer if applicable. @@ -163,7 +181,7 @@ Released 03 May 2021 for Stardew Valley 1.5.4 or later. See [release highlights] * Fixed update subkeys not working in file descriptions for Nexus mods marked as adult content. ## 3.9.5 -Released 21 March 2021 for Stardew Valley 1.5.4 or later. +Released 21 March 2021 for Stardew Valley 1.5.4. * For players: * Added console command to reset community center bundles _(in Console Commands)_. @@ -184,13 +202,13 @@ Released 21 March 2021 for Stardew Valley 1.5.4 or later. _Note: mods don't need to handle the difference in most cases, but some players may use MonoGame on Windows in upcoming versions. Mods which check `Constants.TargetPlatform` should review usages as needed._ ## 3.9.4 -Released 07 March 2021 for Stardew Valley 1.5.4 or later. +Released 07 March 2021 for Stardew Valley 1.5.4. * For players: * Fixed installer error if the `Mods` folder doesn't exist in 3.9.3. ## 3.9.3 -Released 07 March 2021 for Stardew Valley 1.5.4 or later. +Released 07 March 2021 for Stardew Valley 1.5.4. * For players: * Added descriptive error if possible when a `PathTooLongException` crashes SMAPI or the installer. @@ -206,7 +224,7 @@ Released 07 March 2021 for Stardew Valley 1.5.4 or later. * Updated the JSON validator/schema for Content Patcher 1.21. ## 3.9.2 -Released 21 February 2021 for Stardew Valley 1.5.4 or later. +Released 21 February 2021 for Stardew Valley 1.5.4. * For players: * Added more aggressive memory optimization to reduce `OutOfMemoryException` errors with some mods. @@ -229,14 +247,14 @@ Released 21 February 2021 for Stardew Valley 1.5.4 or later. * Fixed SMAPI toolkit defaulting the mod type incorrectly if a mod's `manifest.json` has neither `EntryDll` nor `ContentPackFor`. This only affects external tools, since SMAPI itself validates those fields separately. ## 3.9.1 -Released 25 January 2021 for Stardew Valley 1.5.4 or later. +Released 25 January 2021 for Stardew Valley 1.5.4. * For players: * Fixed _tile contains an invalid TileSheet reference_ crash after mods change certain maps. * Fixed _patched game code_ issue shown for the bundled Error Handler mod. ## 3.9 -Released 22 January 2021 for Stardew Valley 1.5.4 or later. See [release highlights](https://www.patreon.com/posts/46553874). +Released 22 January 2021 for Stardew Valley 1.5.4. See [release highlights](https://www.patreon.com/posts/46553874). * For players: * Updated for Stardew Valley 1.5.4. diff --git a/src/SMAPI.Installer/SMAPI.Installer.csproj b/src/SMAPI.Installer/SMAPI.Installer.csproj index 1777be5f..c47f3e6e 100644 --- a/src/SMAPI.Installer/SMAPI.Installer.csproj +++ b/src/SMAPI.Installer/SMAPI.Installer.csproj @@ -2,7 +2,7 @@ <PropertyGroup> <RootNamespace>StardewModdingAPI.Installer</RootNamespace> <Description>The SMAPI installer for players.</Description> - <TargetFramework>net45</TargetFramework> + <TargetFramework>net452</TargetFramework> <OutputType>Exe</OutputType> <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath> </PropertyGroup> diff --git a/src/SMAPI.Internal/ExceptionExtensions.cs b/src/SMAPI.Internal/ExceptionHelper.cs index d8189048..4bc55f95 100644 --- a/src/SMAPI.Internal/ExceptionExtensions.cs +++ b/src/SMAPI.Internal/ExceptionHelper.cs @@ -1,10 +1,11 @@ using System; using System.Reflection; +using System.Text.RegularExpressions; namespace StardewModdingAPI.Internal { /// <summary>Provides extension methods for handling exceptions.</summary> - internal static class ExceptionExtensions + internal static class ExceptionHelper { /********* ** Public methods @@ -15,20 +16,26 @@ namespace StardewModdingAPI.Internal { try { + string message; switch (exception) { case TypeLoadException ex: - return $"Failed loading type '{ex.TypeName}': {exception}"; + message = $"Failed loading type '{ex.TypeName}': {exception}"; + break; case ReflectionTypeLoadException ex: string summary = ex.ToString(); foreach (Exception childEx in ex.LoaderExceptions ?? new Exception[0]) summary += $"\n\n{childEx?.GetLogSummary()}"; - return summary; + message = summary; + break; default: - return exception?.ToString() ?? $"<null exception>\n{Environment.StackTrace}"; + message = exception?.ToString() ?? $"<null exception>\n{Environment.StackTrace}"; + break; } + + return ExceptionHelper.SimplifyExtensionMessage(message); } catch (Exception ex) { @@ -44,5 +51,26 @@ namespace StardewModdingAPI.Internal exception = exception.InnerException; return exception; } + + /// <summary>Simplify common patterns in exception log messages that don't convey useful info.</summary> + /// <param name="message">The log message to simplify.</param> + public static string SimplifyExtensionMessage(string message) + { + // remove namespace for core exception types + message = Regex.Replace( + message, + @"(?:StardewModdingAPI\.Framework\.Exceptions|Microsoft\.Xna\.Framework|System|System\.IO)\.([a-zA-Z]+Exception):", + "$1:" + ); + + // remove unneeded root build paths for SMAPI and Stardew Valley + message = message + .Replace(@"C:\source\_Stardew\SMAPI\src\", "") + .Replace(@"C:\GitlabRunner\builds\Gq5qA5P4\0\ConcernedApe\", ""); + + // remove placeholder info in Linux/macOS stack traces + return message + .Replace(@"<filename unknown>:0", ""); + } } } diff --git a/src/SMAPI.Internal/SMAPI.Internal.projitems b/src/SMAPI.Internal/SMAPI.Internal.projitems index 0ee94a5b..41d356c0 100644 --- a/src/SMAPI.Internal/SMAPI.Internal.projitems +++ b/src/SMAPI.Internal/SMAPI.Internal.projitems @@ -14,6 +14,6 @@ <Compile Include="$(MSBuildThisFileDirectory)ConsoleWriting\ConsoleLogLevel.cs" /> <Compile Include="$(MSBuildThisFileDirectory)ConsoleWriting\IConsoleWriter.cs" /> <Compile Include="$(MSBuildThisFileDirectory)ConsoleWriting\MonitorColorScheme.cs" /> - <Compile Include="$(MSBuildThisFileDirectory)ExceptionExtensions.cs" /> + <Compile Include="$(MSBuildThisFileDirectory)ExceptionHelper.cs" /> </ItemGroup> </Project>
\ No newline at end of file diff --git a/src/SMAPI.ModBuildConfig/SMAPI.ModBuildConfig.csproj b/src/SMAPI.ModBuildConfig/SMAPI.ModBuildConfig.csproj index b18e79d5..93769650 100644 --- a/src/SMAPI.ModBuildConfig/SMAPI.ModBuildConfig.csproj +++ b/src/SMAPI.ModBuildConfig/SMAPI.ModBuildConfig.csproj @@ -2,7 +2,7 @@ <PropertyGroup> <!--build--> <RootNamespace>StardewModdingAPI.ModBuildConfig</RootNamespace> - <TargetFramework>net45</TargetFramework> + <TargetFramework>net452</TargetFramework> <LangVersion>latest</LangVersion> <GeneratePackageOnBuild>true</GeneratePackageOnBuild> diff --git a/src/SMAPI.Mods.ConsoleCommands/SMAPI.Mods.ConsoleCommands.csproj b/src/SMAPI.Mods.ConsoleCommands/SMAPI.Mods.ConsoleCommands.csproj index a187c1ff..528348a0 100644 --- a/src/SMAPI.Mods.ConsoleCommands/SMAPI.Mods.ConsoleCommands.csproj +++ b/src/SMAPI.Mods.ConsoleCommands/SMAPI.Mods.ConsoleCommands.csproj @@ -2,7 +2,7 @@ <PropertyGroup> <AssemblyName>ConsoleCommands</AssemblyName> <RootNamespace>StardewModdingAPI.Mods.ConsoleCommands</RootNamespace> - <TargetFramework>net45</TargetFramework> + <TargetFramework>net452</TargetFramework> <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath> </PropertyGroup> diff --git a/src/SMAPI.Mods.ConsoleCommands/manifest.json b/src/SMAPI.Mods.ConsoleCommands/manifest.json index de223c01..e53bf991 100644 --- a/src/SMAPI.Mods.ConsoleCommands/manifest.json +++ b/src/SMAPI.Mods.ConsoleCommands/manifest.json @@ -1,9 +1,9 @@ { "Name": "Console Commands", "Author": "SMAPI", - "Version": "3.12.6", + "Version": "3.12.7", "Description": "Adds SMAPI console commands that let you manipulate the game.", "UniqueID": "SMAPI.ConsoleCommands", "EntryDll": "ConsoleCommands.dll", - "MinimumApiVersion": "3.12.6" + "MinimumApiVersion": "3.12.7" } diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/DictionaryPatcher.cs b/src/SMAPI.Mods.ErrorHandler/Patches/DictionaryPatcher.cs index 6ad64e16..8ceafcc5 100644 --- a/src/SMAPI.Mods.ErrorHandler/Patches/DictionaryPatcher.cs +++ b/src/SMAPI.Mods.ErrorHandler/Patches/DictionaryPatcher.cs @@ -48,6 +48,11 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches original: AccessTools.Method(dictionaryType, "get_Item") ?? throw new InvalidOperationException($"Can't find method {PatchHelper.GetMethodString(dictionaryType, "get_Item")} to patch."), finalizer: this.GetHarmonyMethod(nameof(DictionaryPatcher.Finalize_GetItem)) ); + + harmony.Patch( + original: AccessTools.Method(dictionaryType, "Add") ?? throw new InvalidOperationException($"Can't find method {PatchHelper.GetMethodString(dictionaryType, "Add")} to patch."), + finalizer: this.GetHarmonyMethod(nameof(DictionaryPatcher.Finalize_Add)) + ); } } } @@ -63,13 +68,31 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches private static Exception Finalize_GetItem(object key, Exception __exception) { if (__exception is KeyNotFoundException) - { - DictionaryPatcher.Reflection - .GetField<string>(__exception, "_message") - .SetValue($"{__exception.Message}\nkey: '{key}'"); - } + DictionaryPatcher.AddKey(__exception, key); return __exception; } + + /// <summary>The method to call after a dictionary insert throws an exception.</summary> + /// <param name="key">The dictionary key being inserted.</param> + /// <param name="__exception">The exception thrown by the wrapped method, if any.</param> + /// <returns>Returns the exception to throw, if any.</returns> + private static Exception Finalize_Add(object key, Exception __exception) + { + if (__exception is ArgumentException) + DictionaryPatcher.AddKey(__exception, key); + + return __exception; + } + + /// <summary>Add the dictionary key to an exception message.</summary> + /// <param name="exception">The exception whose message to edit.</param> + /// <param name="key">The dictionary key.</param> + private static void AddKey(Exception exception, object key) + { + DictionaryPatcher.Reflection + .GetField<string>(exception, "_message") + .SetValue($"{exception.Message}\nkey: '{key}'"); + } } } diff --git a/src/SMAPI.Mods.ErrorHandler/SMAPI.Mods.ErrorHandler.csproj b/src/SMAPI.Mods.ErrorHandler/SMAPI.Mods.ErrorHandler.csproj index eb878bc5..182a978e 100644 --- a/src/SMAPI.Mods.ErrorHandler/SMAPI.Mods.ErrorHandler.csproj +++ b/src/SMAPI.Mods.ErrorHandler/SMAPI.Mods.ErrorHandler.csproj @@ -2,7 +2,7 @@ <PropertyGroup> <AssemblyName>ErrorHandler</AssemblyName> <RootNamespace>StardewModdingAPI.Mods.ErrorHandler</RootNamespace> - <TargetFramework>net45</TargetFramework> + <TargetFramework>net452</TargetFramework> <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath> </PropertyGroup> diff --git a/src/SMAPI.Mods.ErrorHandler/manifest.json b/src/SMAPI.Mods.ErrorHandler/manifest.json index fcb6d7eb..37a7e9bf 100644 --- a/src/SMAPI.Mods.ErrorHandler/manifest.json +++ b/src/SMAPI.Mods.ErrorHandler/manifest.json @@ -1,9 +1,9 @@ { "Name": "Error Handler", "Author": "SMAPI", - "Version": "3.12.6", + "Version": "3.12.7", "Description": "Handles some common vanilla errors to log more useful info or avoid breaking the game.", "UniqueID": "SMAPI.ErrorHandler", "EntryDll": "ErrorHandler.dll", - "MinimumApiVersion": "3.12.6" + "MinimumApiVersion": "3.12.7" } diff --git a/src/SMAPI.Mods.SaveBackup/SMAPI.Mods.SaveBackup.csproj b/src/SMAPI.Mods.SaveBackup/SMAPI.Mods.SaveBackup.csproj index a6f76781..079beb08 100644 --- a/src/SMAPI.Mods.SaveBackup/SMAPI.Mods.SaveBackup.csproj +++ b/src/SMAPI.Mods.SaveBackup/SMAPI.Mods.SaveBackup.csproj @@ -2,7 +2,7 @@ <PropertyGroup> <AssemblyName>SaveBackup</AssemblyName> <RootNamespace>StardewModdingAPI.Mods.SaveBackup</RootNamespace> - <TargetFramework>net45</TargetFramework> + <TargetFramework>net452</TargetFramework> <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath> </PropertyGroup> diff --git a/src/SMAPI.Mods.SaveBackup/manifest.json b/src/SMAPI.Mods.SaveBackup/manifest.json index 1c84b5c2..28c7ec14 100644 --- a/src/SMAPI.Mods.SaveBackup/manifest.json +++ b/src/SMAPI.Mods.SaveBackup/manifest.json @@ -1,9 +1,9 @@ { "Name": "Save Backup", "Author": "SMAPI", - "Version": "3.12.6", + "Version": "3.12.7", "Description": "Automatically backs up all your saves once per day into its folder.", "UniqueID": "SMAPI.SaveBackup", "EntryDll": "SaveBackup.dll", - "MinimumApiVersion": "3.12.6" + "MinimumApiVersion": "3.12.7" } diff --git a/src/SMAPI.Tests/SMAPI.Tests.csproj b/src/SMAPI.Tests/SMAPI.Tests.csproj index 27520baf..8f7bfab4 100644 --- a/src/SMAPI.Tests/SMAPI.Tests.csproj +++ b/src/SMAPI.Tests/SMAPI.Tests.csproj @@ -2,7 +2,7 @@ <PropertyGroup> <AssemblyName>SMAPI.Tests</AssemblyName> <RootNamespace>SMAPI.Tests</RootNamespace> - <TargetFramework>net45</TargetFramework> + <TargetFramework>net452</TargetFramework> <GenerateAssemblyInfo>false</GenerateAssemblyInfo> <LangVersion>latest</LangVersion> </PropertyGroup> diff --git a/src/SMAPI.Tests/WikiClient/ChangeDescriptorTests.cs b/src/SMAPI.Tests/WikiClient/ChangeDescriptorTests.cs new file mode 100644 index 00000000..b896b09c --- /dev/null +++ b/src/SMAPI.Tests/WikiClient/ChangeDescriptorTests.cs @@ -0,0 +1,139 @@ +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using StardewModdingAPI; +using StardewModdingAPI.Toolkit.Framework.Clients.Wiki; + +namespace SMAPI.Tests.WikiClient +{ + /// <summary>Unit tests for <see cref="ChangeDescriptor"/>.</summary> + [TestFixture] + internal class ChangeDescriptorTests + { + /********* + ** Unit tests + *********/ + /**** + ** Constructor + ****/ + [Test(Description = "Assert that Parse sets the expected values for valid and invalid descriptors.")] + public void Parse_SetsExpectedValues_Raw() + { + // 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" }; + IDictionary<string, string> expectedReplace = new Dictionary<string, string> + { + ["XX"] = "YY", + ["XXX"] = "YYY" + }; + string[] expectedErrors = new[] + { + "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." + }; + + // act + ChangeDescriptor parsed = ChangeDescriptor.Parse(rawDescriptor, out string[] errors); + + // assert + Assert.That(parsed.Add, Is.EquivalentTo(expectedAdd), $"{nameof(parsed.Add)} doesn't match the expected value."); + Assert.That(parsed.Remove, Is.EquivalentTo(expectedRemove), $"{nameof(parsed.Replace)} doesn't match the expected value."); + Assert.That(parsed.Replace, Is.EquivalentTo(expectedReplace), $"{nameof(parsed.Replace)} doesn't match the expected value."); + Assert.That(errors, Is.EquivalentTo(expectedErrors), $"{nameof(errors)} doesn't match the expected value."); + } + + [Test(Description = "Assert that Parse sets the expected values for descriptors when a format callback is specified.")] + public void Parse_SetsExpectedValues_Formatted() + { + // 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" }; + IDictionary<string, string> expectedReplace = new Dictionary<string, string> + { + ["1.00"] = "1.0.0", + ["2.0.0"] = "2.0.0-beta" + }; + string[] expectedErrors = new[] + { + "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." + }; + + // act + ChangeDescriptor parsed = ChangeDescriptor.Parse( + rawDescriptor, + out string[] errors, + formatValue: raw => SemanticVersion.TryParse(raw, out ISemanticVersion version) + ? version.ToString() + : raw + ); + + // assert + |
