diff options
| author | Jesse Plamondon-Willard <Pathoschild@users.noreply.github.com> | 2021-10-17 19:53:40 -0400 |
|---|---|---|
| committer | Jesse Plamondon-Willard <Pathoschild@users.noreply.github.com> | 2021-10-17 19:53:40 -0400 |
| commit | d578345cfd53df8a91ae8e0e1346b711332a999a (patch) | |
| tree | fa9a2e6c855a532dbd99910c7cdac1dc468010da | |
| parent | ebe41180c41f544919c03fb3bf6029437a7d65a4 (diff) | |
| parent | f8c9a2929bb42ef7f71fa3a2d258c5566960aa69 (diff) | |
| download | SMAPI-d578345cfd53df8a91ae8e0e1346b711332a999a.tar.gz SMAPI-d578345cfd53df8a91ae8e0e1346b711332a999a.tar.bz2 SMAPI-d578345cfd53df8a91ae8e0e1346b711332a999a.zip | |
Merge branch 'develop' into stable
40 files changed, 721 insertions, 164 deletions
diff --git a/build/common.targets b/build/common.targets index 1f6cf0b6..02142351 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.7</Version> + <Version>3.12.8</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/README.md b/docs/README.md index e7b8a9b3..ecfa6f2b 100644 --- a/docs/README.md +++ b/docs/README.md @@ -66,7 +66,12 @@ Hungarian | ✓ [fully translated](../src/SMAPI/i18n/hu.json) Italian | ✓ [fully translated](../src/SMAPI/i18n/it.json) Japanese | ✓ [fully translated](../src/SMAPI/i18n/ja.json) Korean | ✓ [fully translated](../src/SMAPI/i18n/ko.json) +Polish¹ | ✓ [fully translated](../src/SMAPI/i18n/pl.json) Portuguese | ✓ [fully translated](../src/SMAPI/i18n/pt.json) Russian | ✓ [fully translated](../src/SMAPI/i18n/ru.json) Spanish | ✓ [fully translated](../src/SMAPI/i18n/es.json) +Thai¹ | ✓ [fully translated](../src/SMAPI/i18n/th.json) Turkish | ✓ [fully translated](../src/SMAPI/i18n/tr.json) + +¹ This is a custom language provided by a mod (see [Polish](https://www.nexusmods.com/stardewvalley/mods/3616) +and [Thai](https://www.nexusmods.com/stardewvalley/mods/7052)). diff --git a/docs/release-notes.md b/docs/release-notes.md index 8ed155bd..25b0d740 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,6 +1,23 @@ ← [README](README.md) # Release notes +## 3.12.8 +Released 18 October 2021 for Stardew Valley 1.5.4. + +* For players: + * Fixed mod edits to the farmhouse shifting the player down one tile in some cases. + * Improved translations. Thanks to ellipszist (added Thai) and Zangorr (added Polish)! + _These are custom languages which require Stardew Valley 1.5.5 and the [Polish](https://www.nexusmods.com/stardewvalley/mods/3616) or [Thai](https://www.nexusmods.com/stardewvalley/mods/7052) mod._ + +* 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. diff --git a/docs/technical/smapi.md b/docs/technical/smapi.md index 4be062e2..f454e99c 100644 --- a/docs/technical/smapi.md +++ b/docs/technical/smapi.md @@ -76,10 +76,48 @@ the debugger attached, so you can intercept errors and step through the code bei doesn't work in MonoDevelop on Linux, unfortunately. ### Preparing a release -To prepare a crossplatform SMAPI release, you'll need to compile it on two platforms. See -[crossplatforming info](https://stardewvalleywiki.com/Modding:Modder_Guide/Test_and_Troubleshoot#Testing_on_all_platforms) -on the wiki for the first-time setup. - +To prepare a crossplatform SMAPI release, you'll need to compile it on two platforms: Windows and +Linux. The instructions below assume you have Windows 11, but you can adapt them for +a different setup if needed. + +#### Initial setup +First-time setup on Windows: +1. [Install Windows Subsystem for Linux (WSL)](https://docs.microsoft.com/en-us/windows/wsl/install). +2. Install the needed software in WSL: + 1. Run `sudo apt update` to update the package list. + 2. Install [the .NET 5 SDK](https://docs.microsoft.com/en-us/dotnet/core/install/linux-ubuntu) + (for Stardew Valley 1.5.5+) or [`mono-complete`](https://www.mono-project.com/download/stable/) + (for earlier versions). + _You can run `lsb_release -a` to get the Ubuntu version number._ + 3. [Install Steam](https://linuxconfig.org/how-to-install-steam-on-ubuntu-20-04-focal-fossa-linux). + 4. Launch `steam` and install the game like usual. + 5. Download and install your preferred IDE. For the [latest standalone Rider + version](https://www.jetbrains.com/help/rider/Installation_guide.html#prerequisites): + ```sh + wget "<download url here>" -O rider-install.tar.gz + sudo tar -xzvf rider-install.tar.gz -C /opt + ln -s "/opt/JetBrains Rider-<version>/bin/rider.sh" + ./rider.sh + ``` + 3. Clone the SMAPI repo in WSL: + ```sh + git clone https://github.com/Pathoschild/SMAPI.git + ``` + +To compile SMAPI in WSL: +1. Run `./rider.sh` to open the Rider GUI. +2. Use the GUI to compile the solution. + +To launch the game: +1. Open a WSL terminal. +2. Run these commands to start Steam: + ```sh + export TERM=xterm + steam + ``` +3. Launch the game through the Steam UI. + +#### Prepare the release 1. Update the version numbers in `build/common.targets`, `Constants`, and the `manifest.json` for bundled mods. Make sure you use a [semantic version](https://semver.org). Recommended format: @@ -90,8 +128,8 @@ on the wiki for the first-time setup. release | `<version>` | `3.0.0` 2. In Windows: 1. Rebuild the solution with the _release_ solution configuration. - 2. Copy the `bin/SMAPI installer` and `bin/SMAPI installer for developers` folders to Linux/macOS. -4. In Linux/macOS: + 2. Copy the `bin/SMAPI installer` and `bin/SMAPI installer for developers` folders to Linux. +4. In Linux: 1. Rebuild the solution with the _release_ solution configuration. 2. Add the `windows-install.*` files from Windows to the `bin/SMAPI installer` and `bin/SMAPI installer for developers` folders compiled on Linux. 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/ExceptionHelper.cs b/src/SMAPI.Internal/ExceptionHelper.cs index 4bc55f95..05b96c2e 100644 --- a/src/SMAPI.Internal/ExceptionHelper.cs +++ b/src/SMAPI.Internal/ExceptionHelper.cs @@ -65,7 +65,7 @@ namespace StardewModdingAPI.Internal // remove unneeded root build paths for SMAPI and Stardew Valley message = message - .Replace(@"C:\source\_Stardew\SMAPI\src\", "") + .Replace(@"E:\source\_Stardew\SMAPI\src\", "") .Replace(@"C:\GitlabRunner\builds\Gq5qA5P4\0\ConcernedApe\", ""); // remove placeholder info in Linux/macOS stack traces 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 e53bf991..9af3ce5d 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.7", + "Version": "3.12.8", "Description": "Adds SMAPI console commands that let you manipulate the game.", "UniqueID": "SMAPI.ConsoleCommands", "EntryDll": "ConsoleCommands.dll", - "MinimumApiVersion": "3.12.7" + "MinimumApiVersion": "3.12.8" } 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/i18n/pl.json b/src/SMAPI.Mods.ErrorHandler/i18n/pl.json new file mode 100644 index 00000000..05a9d411 --- /dev/null +++ b/src/SMAPI.Mods.ErrorHandler/i18n/pl.json @@ -0,0 +1,4 @@ +{
+ // warning messages
+ "warn.invalid-content-removed": "Nieprawidłowa zawartość została usunięta, aby zapobiec awarii (zobacz konsolę SMAPI po więcej informacji)."
+}
diff --git a/src/SMAPI.Mods.ErrorHandler/i18n/th.json b/src/SMAPI.Mods.ErrorHandler/i18n/th.json new file mode 100644 index 00000000..e2a67dda --- /dev/null +++ b/src/SMAPI.Mods.ErrorHandler/i18n/th.json @@ -0,0 +1,4 @@ +{ + // warning messages + "warn.invalid-content-removed": "ทำการลบเนื้อหาที่ไม่ถูกต้องออก เพื่อป้องกันไฟล์เกมเสียหาย (ดูรายละเอียดที่หน้าคอลโซลของ SMAPI)" +} diff --git a/src/SMAPI.Mods.ErrorHandler/manifest.json b/src/SMAPI.Mods.ErrorHandler/manifest.json index 37a7e9bf..cfbaa661 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.7", + "Version": "3.12.8", "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.7" + "MinimumApiVersion": "3.12.8" } 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 28c7ec14..1efd1d89 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.7", + "Version": "3.12.8", "Description": "Automatically backs up all your saves once per day into its folder.", "UniqueID": "SMAPI.SaveBackup", "EntryDll": "SaveBackup.dll", - "MinimumApiVersion": "3.12.7" + "MinimumApiVersion": "3.12.8" } 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 + 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 Apply returns the expected value for the given descriptor.")] + + // null input + [TestCase(null, "", ExpectedResult = null)] + [TestCase(null, "+Nexus:2400", ExpectedResult = "Nexus:2400")] + [TestCase(null, "-Nexus:2400", ExpectedResult = null)] + + // blank input + [TestCase("", null, ExpectedResult = "")] + [TestCase("", "", ExpectedResult = "")] + + // add value + [TestCase("", "+Nexus:2400", ExpectedResult = "Nexus:2400")] + [TestCase("Nexus:2400", "+Nexus:2400", ExpectedResult = "Nexus:2400")] + [TestCase("Nexus:2400", "Nexus:2400", ExpectedResult = "Nexus:2400")] + [TestCase("Nexus:2400", "+Nexus:2401", ExpectedResult = "Nexus:2400, Nexus:2401")] + [TestCase("Nexus:2400", "Nexus:2401", ExpectedResult = "Nexus:2400, Nexus:2401")] + + // remove value + [TestCase("", "-Nexus:2400", ExpectedResult = "")] + [TestCase("Nexus:2400", "-Nexus:2400", ExpectedResult = "")] + [TestCase("Nexus:2400", "-Nexus:2401", ExpectedResult = "Nexus:2400")] + + // replace value + [TestCase("", "Nexus:2400 → Nexus:2401", ExpectedResult = "")] + [TestCase("Nexus:2400", "Nexus:2400 → Nexus:2401", ExpectedResult = "Nexus:2401")] + [TestCase("Nexus:1", "Nexus: 2400 → Nexus: 2401", ExpectedResult = "Nexus:1")] + + // complex strings + [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) + { + var parsed = ChangeDescriptor.Parse(descriptor, out string[] errors); + + Assert.IsEmpty(errors, "Parsing the descriptor failed."); + + return parsed.ApplyToCopy(input); + } + + [Test(Description = "Assert that ToString returns the expected normalized descriptors.")] + [TestCase(null, ExpectedResult = "")] + [TestCase("", ExpectedResult = "")] + [TestCase("+ Nexus:2400", ExpectedResult = "+Nexus:2400")] + [TestCase(" Nexus:2400 ", ExpectedResult = "+Nexus:2400")] + [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) + { + var parsed = ChangeDescriptor.Parse(descriptor, out string[] errors); + + Assert.IsEmpty(errors, "Parsing the descriptor failed."); + + return parsed.ToString(); + } + } +} |
