mirror of
https://github.com/AtomUI/AtomUI.git
synced 2026-10-19 06:58:57 +08:00
240 lines
8.9 KiB
C#
240 lines
8.9 KiB
C#
using System.Globalization;
|
|
using Avalonia.Controls;
|
|
using Shouldly;
|
|
using Xunit;
|
|
|
|
namespace AtomUI.Localization.Tests;
|
|
|
|
public class LanguageManagerTests
|
|
{
|
|
[Fact]
|
|
public void Manager_Starts_At_Revision_Zero_And_Preserves_Supported_Language_Order()
|
|
{
|
|
var runtime = LanguageManagerTestRuntime.Create();
|
|
|
|
runtime.Manager.Current.CurrentLanguage.ShouldBe(LanguageTags.EnUS);
|
|
runtime.Manager.Current.Revision.ShouldBe(0);
|
|
runtime.Manager.SupportedLanguages.Select(static definition => definition.Tag)
|
|
.ShouldBe([LanguageTags.EnUS, LanguageTags.ZhCN, LanguageTags.ArSA]);
|
|
}
|
|
|
|
[Fact]
|
|
public void ChangeLanguage_Commits_One_Atomic_Revision_With_Culture_And_Direction()
|
|
{
|
|
var runtime = LanguageManagerTestRuntime.Create();
|
|
|
|
var result = runtime.Manager.ChangeLanguage(LanguageTags.ArSA);
|
|
|
|
result.Status.ShouldBe(LanguageChangeStatus.Committed);
|
|
result.OldState.CurrentLanguage.ShouldBe(LanguageTags.EnUS);
|
|
result.OldState.Revision.ShouldBe(0);
|
|
result.NewState.ShouldBeSameAs(runtime.Manager.Current);
|
|
result.NewState.CurrentLanguage.ShouldBe(LanguageTags.ArSA);
|
|
result.NewState.FormattingCulture.Name.ShouldBe("ar-SA");
|
|
result.NewState.TextDirection.ShouldBe(LanguageTextDirection.RightToLeft);
|
|
result.NewState.Revision.ShouldBe(1);
|
|
runtime.Localizer.Get(RuntimeResourceKind.Value).ShouldBe("العربية");
|
|
}
|
|
|
|
[Fact]
|
|
public void ChangeLanguage_To_Current_Language_Is_A_Silent_NoOp()
|
|
{
|
|
var runtime = LanguageManagerTestRuntime.Create();
|
|
var resourceNotifications = 0;
|
|
var languageEvents = 0;
|
|
var host = new Border();
|
|
host.Resources.MergedDictionaries.Add(runtime.Provider);
|
|
((IResourceHost)host).ResourcesChanged += (_, _) => resourceNotifications++;
|
|
runtime.Manager.LanguageChanged += (_, _) => languageEvents++;
|
|
|
|
var result = runtime.Manager.ChangeLanguage(LanguageTags.EnUS);
|
|
|
|
result.Status.ShouldBe(LanguageChangeStatus.NoOp);
|
|
result.OldState.ShouldBeSameAs(result.NewState);
|
|
result.NewState.ShouldBeSameAs(runtime.Manager.Current);
|
|
result.NewState.Revision.ShouldBe(0);
|
|
resourceNotifications.ShouldBe(0);
|
|
languageEvents.ShouldBe(0);
|
|
}
|
|
|
|
[Fact]
|
|
public void ChangeLanguage_Rejects_Default_And_Unsupported_Tags_Without_Publishing()
|
|
{
|
|
var runtime = LanguageManagerTestRuntime.Create();
|
|
var events = 0;
|
|
runtime.Manager.LanguageChanged += (_, _) => events++;
|
|
|
|
Should.Throw<ArgumentException>(() => runtime.Manager.ChangeLanguage(default));
|
|
var exception = Should.Throw<LanguageNotSupportedException>(() =>
|
|
runtime.Manager.ChangeLanguage(LanguageTags.FrFR));
|
|
|
|
exception.Language.ShouldBe(LanguageTags.FrFR);
|
|
runtime.Manager.Current.CurrentLanguage.ShouldBe(LanguageTags.EnUS);
|
|
runtime.Manager.Current.Revision.ShouldBe(0);
|
|
events.ShouldBe(0);
|
|
}
|
|
|
|
[Fact]
|
|
public void ChangeLanguage_Requires_The_UI_Thread()
|
|
{
|
|
var runtime = LanguageManagerTestRuntime.Create(checkAccess: static () => false);
|
|
|
|
var exception = Should.Throw<InvalidOperationException>(() =>
|
|
runtime.Manager.ChangeLanguage(LanguageTags.ZhCN));
|
|
|
|
exception.Message.ShouldContain("UI thread");
|
|
runtime.Manager.Current.CurrentLanguage.ShouldBe(LanguageTags.EnUS);
|
|
}
|
|
|
|
[Fact]
|
|
public void Disposed_Manager_Rejects_Changes_And_Clears_Event_Subscribers()
|
|
{
|
|
var runtime = LanguageManagerTestRuntime.Create();
|
|
var events = 0;
|
|
runtime.Manager.LanguageChanged += (_, _) => events++;
|
|
|
|
runtime.Manager.Dispose();
|
|
|
|
var exception = Should.Throw<InvalidOperationException>(() =>
|
|
runtime.Manager.ChangeLanguage(LanguageTags.ZhCN));
|
|
exception.Message.ShouldContain("disposed");
|
|
events.ShouldBe(0);
|
|
}
|
|
|
|
[Fact]
|
|
public void Committed_Change_Publishes_Resources_Before_One_Language_Event()
|
|
{
|
|
var runtime = LanguageManagerTestRuntime.Create();
|
|
var order = new List<string>();
|
|
var host = new Border();
|
|
host.Resources.MergedDictionaries.Add(runtime.Provider);
|
|
((IResourceHost)host).ResourcesChanged += (_, _) =>
|
|
{
|
|
order.Add("resources");
|
|
runtime.Manager.Current.CurrentLanguage.ShouldBe(LanguageTags.ZhCN);
|
|
runtime.Provider.TryGetResource(RuntimeResourceKind.Value, null, out var value).ShouldBeTrue();
|
|
value.ShouldBe("中文");
|
|
};
|
|
runtime.Manager.LanguageChanged += (_, args) =>
|
|
{
|
|
order.Add("event");
|
|
args.Result.NewState.ShouldBeSameAs(runtime.Manager.Current);
|
|
};
|
|
|
|
runtime.Manager.ChangeLanguage(LanguageTags.ZhCN);
|
|
|
|
order.ShouldBe(["resources", "event"]);
|
|
}
|
|
|
|
[Fact]
|
|
public void Throwing_Language_Event_Subscriber_Does_Not_Block_Later_Subscribers()
|
|
{
|
|
var runtime = LanguageManagerTestRuntime.Create();
|
|
var laterSubscriberCalled = false;
|
|
runtime.Manager.LanguageChanged += (_, _) => throw new TestSubscriberException();
|
|
runtime.Manager.LanguageChanged += (_, _) => laterSubscriberCalled = true;
|
|
|
|
var result = runtime.Manager.ChangeLanguage(LanguageTags.ZhCN);
|
|
|
|
result.Status.ShouldBe(LanguageChangeStatus.Committed);
|
|
runtime.Manager.Current.CurrentLanguage.ShouldBe(LanguageTags.ZhCN);
|
|
laterSubscriberCalled.ShouldBeTrue();
|
|
}
|
|
|
|
[Fact]
|
|
public void Manager_Reuses_The_Same_Resource_Provider_Across_Changes()
|
|
{
|
|
var runtime = LanguageManagerTestRuntime.Create();
|
|
var provider = runtime.Provider;
|
|
var host = new Border();
|
|
host.Resources.MergedDictionaries.Add(provider);
|
|
|
|
runtime.Manager.ChangeLanguage(LanguageTags.ZhCN);
|
|
runtime.Manager.ChangeLanguage(LanguageTags.ArSA);
|
|
|
|
runtime.Provider.ShouldBeSameAs(provider);
|
|
provider.Owner.ShouldBeSameAs(host);
|
|
provider.TryGetResource(RuntimeResourceKind.Value, null, out var value).ShouldBeTrue();
|
|
value.ShouldBe("العربية");
|
|
}
|
|
|
|
private sealed class TestSubscriberException : Exception;
|
|
}
|
|
|
|
internal sealed record LanguageManagerTestRuntime(
|
|
LanguageManager Manager,
|
|
LanguageResourceProvider Provider,
|
|
Localizer Localizer)
|
|
{
|
|
internal static LanguageManagerTestRuntime Create(Func<bool>? checkAccess = null)
|
|
{
|
|
var catalog = new LanguageCatalogDescriptor<RuntimeResourceKind>(
|
|
"Acme:Acme.RuntimeResourceKind",
|
|
[new LanguageCatalogUnitDescriptor("Value")],
|
|
static key => key == RuntimeResourceKind.Value ? 0 : -1);
|
|
var builder = new LocalizationBuilder();
|
|
builder.AddCatalog(catalog);
|
|
builder.AddTranslationBundle(CreateBundle(catalog, LanguageTags.EnUS, "English"));
|
|
builder.AddTranslationBundle(CreateBundle(catalog, LanguageTags.ZhCN, "中文"));
|
|
builder.AddTranslationBundle(CreateBundle(catalog, LanguageTags.ArSA, "العربية"));
|
|
var registry = builder.FreezeRegistry();
|
|
|
|
LanguageDefinition[] definitions =
|
|
[
|
|
new(
|
|
LanguageTags.EnUS,
|
|
CultureInfo.GetCultureInfo("en-US"),
|
|
"English",
|
|
LanguageTextDirection.LeftToRight),
|
|
new(
|
|
LanguageTags.ZhCN,
|
|
CultureInfo.GetCultureInfo("zh-CN"),
|
|
"简体中文",
|
|
LanguageTextDirection.LeftToRight),
|
|
new(
|
|
LanguageTags.ArSA,
|
|
CultureInfo.GetCultureInfo("ar-SA"),
|
|
"العربية",
|
|
LanguageTextDirection.RightToLeft)
|
|
];
|
|
var snapshots = definitions.ToDictionary(
|
|
static definition => definition.Tag,
|
|
definition => LanguageSnapshotBuilder.Build(registry, definition.Tag, definition));
|
|
var initialState = new LanguageState(
|
|
LanguageTags.EnUS,
|
|
definitions[0].FormattingCulture,
|
|
definitions[0].TextDirection,
|
|
revision: 0);
|
|
var context = new LanguageContext(
|
|
registry,
|
|
new LanguageRevision(snapshots[LanguageTags.EnUS], initialState));
|
|
var provider = new LanguageResourceProvider(context);
|
|
var manager = new LanguageManager(
|
|
context,
|
|
snapshots,
|
|
definitions,
|
|
provider,
|
|
checkAccess ?? (static () => true));
|
|
|
|
return new LanguageManagerTestRuntime(manager, provider, new Localizer(context));
|
|
}
|
|
|
|
private static TranslationBundleDescriptor CreateBundle(
|
|
LanguageCatalogDescriptor catalog,
|
|
LanguageTag language,
|
|
string value)
|
|
{
|
|
return new TranslationBundleDescriptor(
|
|
catalog.CatalogId,
|
|
language,
|
|
TranslationSourceKind.ModuleBuiltIn,
|
|
"Acme",
|
|
[value]);
|
|
}
|
|
}
|
|
|
|
internal enum RuntimeResourceKind
|
|
{
|
|
Value
|
|
}
|