using System; using System.Collections.Generic; using NUnit.Framework; using StardewModdingAPI.Framework; using StardewValley; namespace StardewModdingAPI.Tests { /// Unit tests for and . [TestFixture] public class TranslationTests { /********* ** Data *********/ /// Sample translation text for unit tests. public static string[] Samples = { null, "", " ", "boop", " boop " }; /// A token structure type. public enum TokenType { /// The tokens are passed in a dictionary. Dictionary, /// The tokens are passed in an anonymous object. AnonymousObject } /********* ** Unit tests *********/ /**** ** Translation helper ****/ [Test(Description = "Assert that the translation helper correctly handles no translations.")] public void Helper_HandlesNoTranslations() { // arrange var data = new Dictionary>(); // act ITranslationHelper helper = new TranslationHelper("ModName", "en", LocalizedContentManager.LanguageCode.en).SetTranslations(data); Translation translation = helper.Get("key"); // 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(helper.GetTranslations(), "The full list of translations is unexpectedly null."); Assert.AreEqual(0, helper.GetTranslations().Count, "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."); } [Test(Description = "Assert that the translation helper returns the expected translations correctly.")] public void Helper_GetTranslations_ReturnsExpectedText() { // arrange var data = this.GetSampleData(); var expected = this.GetExpectedTranslations(); // act var actual = new Dictionary>(); TranslationHelper helper = new TranslationHelper("ModName", "en", LocalizedContentManager.LanguageCode.en).SetTranslations(data); foreach (string locale in expected.Keys) { this.AssertSetLocale(helper, locale, LocalizedContentManager.LanguageCode.en); actual[locale] = helper.GetTranslations(); } // assert foreach (string locale in expected.Keys) { Assert.IsNotNull(actual[locale], $"The translations for {locale} is unexpectedly null."); Assert.That(actual[locale], Is.EquivalentTo(expected[locale]), $"The translations for {locale} don't match the expected values."); } } [Test(Description = "Assert that the translations returned by the helper has the expected text.")] public void Helper_Get_ReturnsExpectedText() { // arrange var data = this.GetSampleData(); var expected = this.GetExpectedTranslations(); // act var actual = new Dictionary>(); TranslationHelper helper = new TranslationHelper("ModName", "en", LocalizedContentManager.LanguageCode.en).SetTranslations(data); foreach (string locale in expected.Keys) { this.AssertSetLocale(helper, locale, LocalizedContentManager.LanguageCode.en); actual[locale] = new Dictionary(); foreach (string key in expected[locale].Keys) actual[locale][key] = helper.Get(key); } // assert foreach (string locale in expected.Keys) { Assert.IsNotNull(actual[locale], $"The translations for {locale} is unexpectedly null."); Assert.That(actual[locale], Is.EquivalentTo(expected[locale]), $"The translations for {locale} don't match the expected values."); } } /**** ** Translation ****/ [Test(Description = "Assert that HasValue returns the expected result for various inputs.")] [TestCase(null, ExpectedResult = false)] [TestCase("", ExpectedResult = false)] [TestCase(" ", ExpectedResult = true)] [TestCase("boop", ExpectedResult = true)] [TestCase(" boop ", ExpectedResult = true)] public bool Translation_HasValue(string text) { return new Translation("ModName", "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) { // act Translation translation = new Translation("ModName", "pt-BR", "key", text); // assert if (translation.HasValue()) Assert.AreEqual(text, translation.ToString(), "The translation returned an unexpected value given a valid input."); else Assert.AreEqual(this.GetPlaceholderText("key"), translation.ToString(), "The translation returned an unexpected value given a null or empty input."); } [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) { // act Translation translation = new Translation("ModName", "pt-BR", "key", text); // assert if (translation.HasValue()) 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."); } [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) { // act Translation translation = new Translation("ModName", "pt-BR", "key", text).UsePlaceholder(value); // assert if (translation.HasValue()) Assert.AreEqual(text, translation.ToString(), "The translation returned an unexpected value given a valid input."); else if (!value) Assert.AreEqual(text, translation.ToString(), "The translation returned an unexpected value given a null or empty input with the placeholder disabled."); else Assert.AreEqual(this.GetPlaceholderText("key"), translation.ToString(), "The translation returned an unexpected value given a null or empty input with the placeholder enabled."); } [Test(Description = "Assert that the translation's Assert method throws the expected exception.")] public void Translation_Assert([ValueSource(nameof(TranslationTests.Samples))] string text) { // act Translation translation = new Translation("ModName", "pt-BR", "key", text); // assert if (translation.HasValue()) Assert.That(() => translation.Assert(), Throws.Nothing, "The assert unexpected threw an exception for a valid input."); else Assert.That(() => translation.Assert(), Throws.Exception.TypeOf(), "The assert didn't throw an exception for invalid input."); } [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) { // act Translation translation = new Translation("ModName", "pt-BR", "key", text).Default(@default); // assert if (!string.IsNullOrEmpty(text)) Assert.AreEqual(text, translation.ToString(), "The translation returned an unexpected value given a valid base text."); else if (!string.IsNullOrEmpty(@default)) Assert.AreEqual(@default, translation.ToString(), "The translation returned an unexpected value given a null or empty base text, but valid default."); else Assert.AreEqual(this.GetPlaceholderText("key"), translation.ToString(), "The translation returned an unexpected value given a null or empty base and default text."); } /**** ** Translation tokens ****/ [Test(Description = "Assert that multiple translation tokens are replaced correctly regardless of the token structure.")] public void Translation_Tokens([Values(TokenType.AnonymousObject, TokenType.Dictionary)] TokenType tokenType) { // arrange string start = Guid.NewGuid().ToString("N"); string middle = Guid.NewGuid().ToString("N"); string end = Guid.NewGuid().ToString("N"); const string input = "{{start}} tokens are properly replaced (including {{middle}} {{ MIDdlE}}) {{end}}"; string expected = $"{start} tokens are properly replaced (including {middle} {middle}) {end}"; // act Translation translation = new Translation("ModName", "pt-BR", "key", input); switch (tokenType) { case TokenType.AnonymousObject: translation = translation.Tokens(new { start, middle, end }); break; case TokenType.Dictionary: translation = translation.Tokens(new Dictionary { ["start"] = start, ["middle"] = middle, ["end"] = end }); break; default: throw new NotSupportedException($"Unknown token type {tokenType}."); } // assert Assert.AreEqual(expected, translation.ToString(), "The translation returned an unexpected text."); } [Test(Description = "Assert that the translation can replace tokens in all valid formats.")] [TestCase("{{value}}", "value")] [TestCase("{{ value }}", "value")] [TestCase("{{value }}", "value")] [TestCase("{{ the_value }}", "the_value")] [TestCase("{{ the.value_here }}", "the.value_here")] [TestCase("{{ the_value-here.... }}", "the_value-here....")] [TestCase("{{ tHe_vALuE-HEre.... }}", "tHe_vALuE-HEre....")] public void Translation_Tokens_ValidFormats(string text, string key) { // arrange string value = Guid.NewGuid().ToString("N"); // act Translation translation = new Translation("ModName", "pt-BR", "key", text).Tokens(new Dictionary { [key] = value }); // assert Assert.AreEqual(value, translation.ToString(), "The translation returned an unexpected value given a valid base text."); } [Test(Description = "Assert that translation tokens are case-insensitive and surrounding-whitespace-insensitive.")] [TestCase("{{value}}", "value")] [TestCase("{{VaLuE}}", "vAlUe")] [TestCase("{{VaLuE }}", " vAlUe")] public void Translation_Tokens_KeysAreNormalised(string text, string key) { // arrange string value = Guid.NewGuid().ToString("N"); // act Translation translation = new Translation("ModName", "pt-BR", "key", text).Tokens(new Dictionary { [key] = value }); // assert Assert.AreEqual(value, translation.ToString(), "The translation returned an unexpected value given a valid base text."); } /********* ** Private methods *********/ /// Set a translation helper's locale and assert that it was set correctly. /// The translation helper to change. /// The expected locale. /// The expected game language code. private void AssertSetLocale(TranslationHelper helper, string locale, LocalizedContentManager.LanguageCode localeEnum) { helper.SetLocale(locale, localeEnum); Assert.AreEqual(locale, helper.Locale, "The locale doesn't match the input value."); Assert.AreEqual(localeEnum, helper.LocaleEnum, "The locale enum doesn't match the input value."); } /// Get sample raw translations to input. private IDictionary> GetSampleData() { return new Dictionary> { ["default"] = new Dictionary { ["key A"] = "default A", ["key C"] = "default C" }, ["en"] = new Dictionary { ["key A"] = "en A", ["key B"] = "en B" }, ["en-US"] = new Dictionary(), ["zzz"] = new Dictionary { ["key A"] = "zzz A" } }; } /// Get the expected translation output given , based on the expected locale fallback. private IDictionary> GetExpectedTranslations() { return new Dictionary> { ["default"] = new Dictionary { ["key A"] = "default A", ["key C"] = "default C" }, ["en"] = new Dictionary { ["key A"] = "en A", ["key B"] = "en B", ["key C"] = "default C" }, ["en-us"] = new Dictionary { ["key A"] = "en A", ["key B"] = "en B", ["key C"] = "default C" }, ["zzz"] = new Dictionary { ["key A"] = "zzz A", ["key C"] = "default C" } }; } /// Get the default placeholder text when a translation is missing. /// The translation key. private string GetPlaceholderText(string key) { return string.Format(Translation.PlaceholderText, key); } } }