using System.Collections; using System.Globalization; using System.IO; using System.Reflection; using System.Resources; using System.Text.RegularExpressions; using CursorLang.Models; namespace CursorLang.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 partial class StringsTests { private static readonly ResourceManager Resources = new("CursorLang.Resources.Strings", typeof(App).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); } } /// /// Every key the settings window markup asks for has to exist in the /// resources: otherwise the user sees the key itself in its place. /// [Fact] public void Every_key_from_the_settings_window_markup_exists_in_the_resources() { HashSet known = [.. NeutralKeys()]; List missing = []; foreach (Match match in LocalizationBinding().Matches(ReadSettingsWindowMarkup())) { string key = match.Groups["key"].Value; if (!known.Contains(key)) { missing.Add(key); } } Assert.Empty(missing); } /// /// The version of an update is put into the string by the app, so the place /// for it has to be there in both languages. /// [Fact] public void The_string_about_an_available_update_has_room_for_the_version() { Assert.Contains("{0}", Resources.GetString("UpdateAvailable", English), StringComparison.Ordinal); Assert.Contains("{0}", Resources.GetString("UpdateAvailable", Russian), StringComparison.Ordinal); } /// The markup does ask for strings — otherwise the check above means nothing. [Fact] public void The_settings_window_markup_asks_for_resource_strings() { Assert.NotEmpty(LocalizationBinding().Matches(ReadSettingsWindowMarkup())); } 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); } 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)!; private static string ReadSettingsWindowMarkup() { using Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("MainWindow.xaml") ?? throw new InvalidOperationException("The settings window markup is not embedded in the test assembly"); using var reader = new StreamReader(stream); return reader.ReadToEnd(); } [GeneratedRegex(@"Localization\[(?\w+)\]")] private static partial Regex LocalizationBinding(); }