using System.Collections; using System.Globalization; using System.Resources; using CursorLang.Core.Models; using CursorLang.Core.Services; namespace CursorLang.Core.Tests.Resources; /// /// Checks of the resources themselves: they carry every caption in the settings /// window, and a missing key only shows on a live window. /// public sealed class StringsTests { private static readonly ResourceManager Resources = new("CursorLang.Core.Resources.Strings", typeof(LocalizationService).Assembly); private static readonly CultureInfo English = CultureInfo.GetCultureInfo("en"); private static readonly CultureInfo Russian = CultureInfo.GetCultureInfo("ru"); [Theory] [MemberData(nameof(EnumKeys))] public void Every_list_value_has_an_English_caption(string key) { Assert.False(string.IsNullOrWhiteSpace(Resources.GetString(key, English))); } [Theory] [MemberData(nameof(EnumKeys))] public void Every_list_value_has_a_Russian_caption(string key) { Assert.False(string.IsNullOrWhiteSpace(Resources.GetString(key, Russian))); } [Fact] public void The_Russian_translation_covers_every_string() { List missing = []; foreach (string key in NeutralKeys()) { // An untranslated resource falls back to English, so the Russian set // is asked directly rather than through the string with a fallback if (RussianSet().GetString(key) is null) { missing.Add(key); } } Assert.Empty(missing); } [Fact] public void The_Russian_translation_has_no_extra_strings() { HashSet neutral = [.. NeutralKeys()]; List extra = []; foreach (DictionaryEntry entry in RussianSet()) { var key = (string)entry.Key; if (!neutral.Contains(key)) { extra.Add(key); } } Assert.Empty(extra); } [Fact] public void There_are_no_empty_strings_in_the_resources() { foreach (string key in NeutralKeys()) { Assert.False(string.IsNullOrWhiteSpace(Resources.GetString(key, English)), key); Assert.False(string.IsNullOrWhiteSpace(Resources.GetString(key, Russian)), key); } } /// /// The version is put into the title by the app, so the place for it has to /// be there in both languages: the title is the only place it is shown, and a /// translation without the placeholder would quietly drop it. /// [Fact] public void The_title_of_the_window_has_room_for_the_version() { Assert.Contains("{0}", Resources.GetString("SettingsTitle", English), StringComparison.Ordinal); Assert.Contains("{0}", Resources.GetString("SettingsTitle", Russian), StringComparison.Ordinal); } public static TheoryData EnumKeys() { var data = new TheoryData(); foreach (string key in EnumKeysOf()) { data.Add(key); } foreach (string key in EnumKeysOf()) { data.Add(key); } foreach (string key in EnumKeysOf()) { data.Add(key); } foreach (string key in EnumKeysOf()) { data.Add(key); } foreach (string key in EnumKeysOf()) { data.Add(key); } return data; } // A caption key is built from the type name and the value: PopupPlacementMode_AtCursor private static IEnumerable EnumKeysOf() where TEnum : struct, Enum => Enum.GetValues().Select(value => $"{typeof(TEnum).Name}_{value}"); private static IEnumerable NeutralKeys() { ResourceSet set = Resources.GetResourceSet(CultureInfo.InvariantCulture, true, true)!; foreach (DictionaryEntry entry in set) { yield return (string)entry.Key; } } private static ResourceSet RussianSet() => Resources.GetResourceSet(Russian, createIfNotExists: true, tryParents: false)!; }