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.
89 lines
4.0 KiB
C#
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();
|
|
}
|