Files
PalladiumWallet/tests/PalladiumWallet.Tests/App/LocalizationTests.cs
T
davide 530b7feaa5 test(app): add headless ViewModel, CLI and localization tests
ViewModels run on Avalonia.Headless: wizard flows, password and file-lock
handling, seed and private-key reveal gating, contacts, settings, sync and
send against the fake server. The CLI entry point is invoked in-process
end to end. Localization checks cover the 7 languages, placeholders and
keys used in sources. Internal seams (UpdateCheck, ProfileOverride) keep
the tests offline.
2026-11-03 11:45:43 +01:00

89 lines
4.0 KiB
C#

using System.Reflection;
using System.Text.RegularExpressions;
using PalladiumWallet.App.Localization;
namespace PalladiumWallet.Tests.App;
/// <summary>
/// Consistency of the in-code translation table: every key must have a non-empty value for
/// each of the 7 languages, format placeholders must match across languages, and every key
/// referenced from XAML or C# must exist (a missing key silently renders as the raw key).
/// </summary>
[Collection("AppPaths")] // shares the serialized collection for process-wide statics (Loc.Instance, AppPaths)
public partial class LocalizationTests
{
private static Dictionary<string, string[]> Table() =>
(Dictionary<string, string[]>)typeof(Loc)
.GetField("Strings", BindingFlags.NonPublic | BindingFlags.Static)!.GetValue(null)!;
[Fact]
public void EveryKey_HasOneNonEmptyValuePerLanguage()
{
var bad = Table()
.Where(kv => kv.Value.Length != Loc.Languages.Length || kv.Value.Any(string.IsNullOrWhiteSpace))
.Select(kv => kv.Key).ToList();
Assert.True(bad.Count == 0, "Incomplete translations: " + string.Join(", ", bad));
}
[Fact]
public void LanguageNames_MatchLanguages() =>
Assert.Equal(Loc.Languages.Length, Loc.LanguageNames.Length);
[Fact]
public void FormatPlaceholders_AreIdenticalAcrossLanguages()
{
var bad = new List<string>();
foreach (var (key, values) in Table())
{
var sets = values.Select(v => string.Join(",", Placeholder().Matches(v).Select(m => m.Value).Distinct().Order())).ToList();
if (sets.Distinct().Count() > 1) bad.Add($"{key}: {string.Join(" | ", sets)}");
}
Assert.True(bad.Count == 0, "Placeholder mismatch: " + string.Join("; ", bad));
}
[Fact]
public void Indexer_ReturnsTranslationPerLanguage_AndFallsBackToKey()
{
try
{
var table = Table();
var key = table.Keys.First();
for (var i = 0; i < Loc.Languages.Length; i++)
Assert.Equal(table[key][i], Loc.SwitchTo(Loc.Languages[i])[key]);
Assert.Equal("no.such.key", Loc.Tr("no.such.key"));
Assert.Equal("en", Loc.SwitchTo("xx").Language); // unknown language → English
}
finally { Loc.SwitchTo("en"); }
}
[Fact]
public void KeysUsedInSources_ExistInTable()
{
var root = RepoRoot();
if (root is null) return; // running from a packaged copy without sources
var keys = Table().Keys.ToHashSet();
var missing = new SortedSet<string>();
foreach (var file in Directory.EnumerateFiles(Path.Combine(root, "src", "App"), "*.*", SearchOption.AllDirectories)
.Where(f => (f.EndsWith(".axaml") || f.EndsWith(".cs")) && !f.Contains($"{Path.DirectorySeparatorChar}obj{Path.DirectorySeparatorChar}")
&& !f.Contains($"{Path.DirectorySeparatorChar}bin{Path.DirectorySeparatorChar}")
&& Path.GetFileName(f) != "Loc.cs")) // Loc.cs only mentions the syntax in comments
{
var text = File.ReadAllText(file);
foreach (Match m in XamlKey().Matches(text)) if (!keys.Contains(m.Groups[1].Value)) missing.Add(m.Groups[1].Value);
foreach (Match m in CodeKey().Matches(text)) if (!keys.Contains(m.Groups[1].Value)) missing.Add(m.Groups[1].Value);
}
Assert.True(missing.Count == 0, "Keys used but not defined: " + string.Join(", ", missing));
}
private static string? RepoRoot()
{
for (var d = new DirectoryInfo(AppContext.BaseDirectory); d is not null; d = d.Parent)
if (File.Exists(Path.Combine(d.FullName, "PalladiumWallet.sln"))) return d.FullName;
return null;
}
[GeneratedRegex(@"\{\d+(?::[^}]*)?\}")] private static partial Regex Placeholder();
[GeneratedRegex(@"Loc\[([A-Za-z0-9_.]+)\]")] private static partial Regex XamlKey();
[GeneratedRegex(@"Loc\.Tr\(""([A-Za-z0-9_.]+)""\)")] private static partial Regex CodeKey();
}