using System; using System.Collections.Generic; using System.Linq; using AibisDream.Framework; using AibisDream.EditorTools; using AibisDream.UI; using NUnit.Framework; using TMPro; using UnityEditor; using UnityEditor.AddressableAssets; using UnityEditor.SceneManagement; using UnityEngine; using UnityEngine.SceneManagement; namespace AibisDream.SystemEditor.Tests { public sealed class TypographyContractTests { private const string ProfileFolder = "Assets/GameContent/Common/Typography/Profiles"; [TestCase(LocaleKind.Cn, TypographyProfileKind.ZhHans)] [TestCase(LocaleKind.Ja, TypographyProfileKind.Japanese)] [TestCase(LocaleKind.En, TypographyProfileKind.Latin)] [TestCase(LocaleKind.Es, TypographyProfileKind.Latin)] [TestCase(LocaleKind.PtBr, TypographyProfileKind.Latin)] [TestCase(LocaleKind.Ru, TypographyProfileKind.Russian)] public void LocaleMapping_UsesExpectedProfile(LocaleKind locale, TypographyProfileKind expected) { Assert.That(TypographyLocaleMapping.GetProfileKind(locale), Is.EqualTo(expected)); Assert.That( TypographyLocaleMapping.GetProfileKey(locale), Is.EqualTo($"Typography/Profile/{expected}")); } [Test] public void Resolve_MissingRoleFallsBackToBody() { TMP_FontAsset font = AssetDatabase.LoadAssetAtPath( "Assets/Font/Assets/ChillBitmap_16px SDF.asset"); var profile = ScriptableObject.CreateInstance(); try { var body = new TypographyStyle(TypographyRole.Body, font); profile.Configure(TypographyProfileKind.Latin, new[] { body }); Assert.That(profile.Resolve(TypographyRole.Dialogue), Is.SameAs(body)); } finally { UnityEngine.Object.DestroyImmediate(profile); } } [Test] public void Binding_ReapplyUsesCapturedBaselineWithoutAccumulatingMetrics() { TMP_FontAsset font = AssetDatabase.LoadAssetAtPath( "Assets/Font/Assets/ChillBitmap_16px SDF.asset"); var profile = ScriptableObject.CreateInstance(); var go = new GameObject("TypographyMetricContract", typeof(RectTransform)); try { TextMeshProUGUI text = go.AddComponent(); text.fontSize = 40f; text.lineSpacing = 3f; text.characterSpacing = 2f; LocalizedTypography binding = go.AddComponent(); profile.Configure( TypographyProfileKind.Latin, new[] { new TypographyStyle( TypographyRole.Body, font, fontSizeMultiplier: 0.75f, lineSpacingOffset: 1f, characterSpacingOffset: 2f) }); binding.ApplyProfile(profile); binding.ApplyProfile(profile); Assert.That(text.fontSize, Is.EqualTo(30f)); Assert.That(text.lineSpacing, Is.EqualTo(4f)); Assert.That(text.characterSpacing, Is.EqualTo(4f)); } finally { UnityEngine.Object.DestroyImmediate(go); UnityEngine.Object.DestroyImmediate(profile); } } [Test] public void FirstRoundProfiles_AreCompleteAndAddressableByConvention() { foreach (TypographyProfileKind kind in Enum.GetValues(typeof(TypographyProfileKind))) { TypographyProfile profile = AssetDatabase.LoadAssetAtPath( $"{ProfileFolder}/{kind}.asset"); Assert.That(profile, Is.Not.Null, kind.ToString()); Assert.That(profile.ProfileKind, Is.EqualTo(kind)); Assert.That(profile.GetValidationErrors(), Is.Empty, kind.ToString()); Assert.That( Enum.GetValues(typeof(TypographyRole)).Cast() .All(role => profile.Resolve(role) != null), Is.True, kind.ToString()); string guid = AssetDatabase.AssetPathToGUID($"{ProfileFolder}/{kind}.asset"); var entry = AddressableAssetSettingsDefaultObject.Settings.FindAssetEntry(guid); Assert.That(entry, Is.Not.Null, kind.ToString()); Assert.That(entry.address, Is.EqualTo($"Typography/Profile/{kind}")); } } [Test] public void TerminalPrefabs_BindEveryTextExactlyOnce() { foreach (string path in TerminalTypographyMigration.PrefabPaths) { GameObject prefab = AssetDatabase.LoadAssetAtPath(path); Assert.That(prefab, Is.Not.Null, path); TMP_Text[] texts = prefab.GetComponentsInChildren(true); Assert.That(texts, Is.Not.Empty, path); foreach (TMP_Text text in texts) { LocalizedTypography[] bindings = text.GetComponents(); Assert.That(bindings, Has.Length.EqualTo(1), $"{path}: {text.name}"); Assert.That(bindings[0].Role, Is.EqualTo(TypographyRole.Terminal), $"{path}: {text.name}"); } } } [Test] public void PersistenceTerminalComposition_InheritsTypographyBindings() { Scene scene = SceneManager.GetSceneByPath(TerminalTypographyMigration.PersistenceScenePath); bool openedForTest = !scene.IsValid() || !scene.isLoaded; if (openedForTest) { scene = EditorSceneManager.OpenScene( TerminalTypographyMigration.PersistenceScenePath, OpenSceneMode.Additive); } try { Component[] hosts = scene.GetRootGameObjects() .SelectMany(root => root.GetComponentsInChildren(true)) .Where(component => component is TerminalPanel || component is InGameTerminalPanel) .ToArray(); Assert.That(hosts, Has.Length.EqualTo(2)); var violations = new List(); foreach (Component host in hosts) { foreach (TMP_Text text in host.GetComponentsInChildren(true)) { LocalizedTypography[] bindings = text.GetComponents(); if (bindings.Length != 1) { violations.Add($"{host.name}: {text.name} has {bindings.Length} bindings"); continue; } if (bindings[0].Role != TypographyRole.Terminal) violations.Add($"{host.name}: {text.name} uses {bindings[0].Role}"); } } Assert.That(violations, Is.Empty, string.Join("\n", violations)); } finally { if (openedForTest) EditorSceneManager.CloseScene(scene, true); } } } }