using System.Globalization;
using MeterVault.App.Localization;
using MeterVault.Core.Domain;
namespace MeterVault.Integration.Tests.Localization;
///
/// Every domain enum value the UI renders has wording in every language (SDD §12, M7).
///
///
/// ends each mapping with a fallback arm that returns the bare identifier,
/// so adding an enum value can never throw mid-render on a dashboard. The cost of that safety is that
/// a forgotten value degrades silently to English — these tests are what make it loud instead, by
/// demanding a matching Enum_<Type>_<Value> resource for every declared value.
///
public sealed class EnumDisplayNameTests
{
[Fact]
public void Every_localized_enum_value_has_a_resource_in_every_language()
{
var missing = new List();
foreach (var type in DisplayNames.LocalizedEnums)
{
foreach (var name in Enum.GetNames(type))
{
// None is the empty bitmask, deliberately rendered as nothing at all.
if (type == typeof(ReadingFlags) && name == nameof(ReadingFlags.None))
{
continue;
}
var key = $"Enum_{type.Name}_{name}";
foreach (var culture in Loc.SupportedCultures)
{
var value = Strings.ResourceManager.GetString(key, CultureInfo.GetCultureInfo(culture));
if (string.IsNullOrWhiteSpace(value))
{
missing.Add($"{key} [{culture}]");
}
}
}
}
Assert.True(missing.Count == 0, $"Missing enum display names: {string.Join(", ", missing)}");
}
[Fact]
public void Display_names_are_translated_rather_than_echoing_the_identifier()
{
// Not every value can differ — "Tasmota", "Bonus" and "Global" are the same word in German —
// but if a whole enum came back as its own identifiers, the mapping was never written.
var untranslated = new List();
foreach (var type in DisplayNames.LocalizedEnums)
{
var names = Enum.GetNames(type);
var translated = 0;
foreach (var name in names)
{
var german = Strings.ResourceManager.GetString($"Enum_{type.Name}_{name}", CultureInfo.GetCultureInfo("de"));
if (german is not null && !string.Equals(german, name, StringComparison.Ordinal))
{
translated++;
}
}
if (translated == 0)
{
untranslated.Add(type.Name);
}
}
Assert.True(untranslated.Count == 0, $"Enums with no German wording at all: {string.Join(", ", untranslated)}");
}
[Theory]
[InlineData("en", "Consumption")]
[InlineData("de", "Verbrauch")]
public void The_reader_s_language_decides_the_wording(string culture, string expected) =>
Assert.Equal(expected, WithUiCulture(culture, () => ConsumptionKind.Consumption.Display()));
[Fact]
public void A_bitmask_lists_the_flags_it_actually_carries()
{
// Blank rather than "None": the readings table has a flag column that is empty on almost
// every row, and printing a word down the whole page is noise, not information.
Assert.Equal(string.Empty, ReadingFlags.None.Display());
var both = WithUiCulture("de", () => (ReadingFlags.CounterReset | ReadingFlags.MeterSwap).Display());
Assert.Equal("Zählerreset, Zählerwechsel", both);
Assert.Equal("Meter swap", WithUiCulture("en", () => ReadingFlags.MeterSwap.Display()));
}
[Fact]
public void An_undeclared_enum_value_degrades_to_its_identifier_instead_of_throwing()
{
// Guards the fallback arm itself: a value cast in from the database (or a future migration)
// must not take a dashboard down.
var unknown = (MeterMode)999;
Assert.Equal("999", unknown.Display());
}
private static T WithUiCulture(string culture, Func body)
{
var previous = CultureInfo.CurrentUICulture;
try
{
CultureInfo.CurrentUICulture = CultureInfo.GetCultureInfo(culture);
return body();
}
finally
{
CultureInfo.CurrentUICulture = previous;
}
}
}