using System.Collections.Generic; using System.Linq; using System.Reflection; using NUnit.Framework; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; using Vanguard.Api; using Vanguard.Api.Contracts; using Vanguard.App; using Vanguard.Auth; using Vanguard.Board3D; using Vanguard.Meta; using Vanguard.Shared.Protocol; using Vanguard.State; using Vanguard.UI; using BattleView = Vanguard.UI.BattleView; namespace Vanguard.Tests { public sealed class ClientSmokeTests { [Test] public void Client_Views_Build() { var config = new AppConfig(); config.Load(); var tokens = new TokenStore(); var api = new ApiClient(config, tokens); var player = new PlayerState(); var auth = new AuthService(api, tokens, player); var meta = new MetaService(api, player); var canvas = UiKit.CreateCanvas("Test Canvas"); var login = new LoginView(canvas.transform, config, auth, () => { }); login.Show("smoke"); var lobby = new MetaView( canvas.transform, auth, player, () => { }, () => { }, () => { }, () => { }, () => { }, () => { }); var onboarding = new OnboardingView( canvas.transform, auth, meta, () => { }, () => { }); var decks = new DeckBuilderView( canvas.transform, meta, auth, () => { }, _ => { }, _ => { }); var queue = new QueueView( canvas.transform, meta, auth, () => { }, _ => { }); var store = new StoreView( canvas.transform, meta, auth, player, () => { }); var history = new HistoryView( canvas.transform, meta, auth, () => { }); var battle = new BattleView( canvas.transform, meta, () => { }); Assert.IsNotNull(GameObject.Find("LoginScreen")); Assert.IsTrue(Exists("MetaScreen")); Assert.IsTrue(Exists("OnboardingScreen")); Assert.IsTrue(Exists("DeckBuilderScreen")); Assert.IsTrue(Exists("StoreScreen")); Assert.IsTrue(Exists("HistoryScreen")); Assert.IsTrue(Exists("ReplayScreen")); Assert.IsTrue(Exists("QueueScreen")); Assert.IsTrue(Exists("BattleScreen")); Assert.IsNotNull( Resources.Load("CardArt/td01_005"), "TD01 card art is not imported"); var standardBack = Resources.Load("CardArt/vanguard_back"); Assert.IsNotNull(standardBack, "Standard Vanguard card back is not imported"); Assert.AreEqual(standardBack, CardArt.Back.texture); var artGo = new GameObject("ArtProbe", typeof(RectTransform), typeof(Image)); var artImage = artGo.GetComponent(); CardImageService.Apply(artImage, "td01_005", null); Assert.IsNotNull(artImage.sprite, "CardImageService did not load real card art"); Object.DestroyImmediate(artGo); // После смены сцены старые views не должны реагировать на PlayerState. lobby.Dispose(); store.Dispose(); player.Apply(new MeResponse { UserName = "after-dispose", Gold = 10, Dust = 2, Tokens = 1, }); Cleanup(); } [Test] public void Localization_Translates_Static_And_Dynamic_Strings() { Loc.SetLanguage(UiLanguage.Ru); Assert.AreEqual("Коллекция", Loc.Translate("Коллекция")); Loc.SetLanguage(UiLanguage.En); Assert.AreEqual("Collection", Loc.Translate("Коллекция")); Assert.AreEqual("Grade: all", Loc.Translate("Ранг: все")); Assert.AreEqual("Cards in collection: 12", Loc.Translate("Карт в коллекции: 12")); Assert.AreEqual("Victory!", Loc.Translate("Победа!")); Assert.AreEqual("Sentinel: yes · Boost: no", Loc.Translate("Sentinel: да · Boost: нет")); Loc.SetLanguage(UiLanguage.Ru); var go = new GameObject("LocalizedLabel", typeof(RectTransform), typeof(Text)); var label = go.GetComponent(); var localized = go.AddComponent(); localized.Initialize("Коллекция"); Assert.AreEqual("Коллекция", label.text); Loc.SetLanguage(UiLanguage.En); Assert.AreEqual("Collection", label.text); label.text = "Магазин"; localized.ApplyNow(); Assert.AreEqual("Store", label.text); Loc.SetLanguage(UiLanguage.Ru); Assert.AreEqual("Магазин", label.text); Object.DestroyImmediate(go); } [Test] public void Canvas_Reference_Resolution_Is_1920x1080() { var canvas = UiKit.CreateCanvas("Reference Resolution Canvas"); try { var scaler = canvas.GetComponent(); Assert.IsNotNull(scaler); Assert.AreEqual(1920f, scaler.referenceResolution.x); Assert.AreEqual(1080f, scaler.referenceResolution.y); } finally { Object.DestroyImmediate(canvas.gameObject); var eventSystem = Object.FindAnyObjectByType(); if (eventSystem != null) Object.DestroyImmediate(eventSystem.gameObject); } } [Test] public void Battle_Layout_Fits_Reference_Resolution() { var config = new AppConfig(); config.Load(); var tokens = new TokenStore(); var api = new ApiClient(config, tokens); var player = new PlayerState(); var auth = new AuthService(api, tokens, player); var meta = new MetaService(api, player); var canvas = UiKit.CreateCanvas("Layout Test Canvas"); try { var battle = new BattleView(canvas.transform, meta, () => { }); var screen = Resources.FindObjectsOfTypeAll() .First(go => go.name == "BattleScreen"); screen.SetActive(true); var self = ReferenceRect(FindRect("ZoneBlocksSelf")); var opponent = ReferenceRect(FindRect("ZoneBlocksOpponent")); var phase = ReferenceRect(FindRect("PhasePanel")); var stats = ReferenceRect(FindRect("CombatStats")); AssertWithinScreen(self, "self zones"); AssertWithinScreen(opponent, "opponent zones"); AssertWithinScreen(phase, "phase panel"); AssertWithinScreen(stats, "combat stats"); Assert.IsFalse(self.Overlaps(opponent)); Assert.IsFalse(self.Overlaps(phase)); Assert.IsFalse(opponent.Overlaps(phase)); Assert.IsFalse(opponent.Overlaps(stats)); Assert.IsFalse(phase.Overlaps(stats)); } finally { Object.DestroyImmediate(canvas.gameObject); Cleanup(); } } private static RectTransform FindRect(string name) => Resources.FindObjectsOfTypeAll() .Where(go => go.scene.IsValid() && go.name == name) .Select(go => go.GetComponent()) .First(rect => rect != null); private static Rect ReferenceRect(RectTransform rect) { var size = rect.sizeDelta; var anchor = new Vector2( Mathf.Lerp(0f, 1920f, rect.anchorMin.x), Mathf.Lerp(0f, 1080f, rect.anchorMin.y)); return new Rect( anchor + rect.anchoredPosition - Vector2.Scale(size, rect.pivot), size); } private static void AssertWithinScreen(Rect rect, string name) { Assert.GreaterOrEqual(rect.xMin, 0f, name + " left"); Assert.GreaterOrEqual(rect.yMin, 0f, name + " bottom"); Assert.LessOrEqual(rect.xMax, 1920f, name + " right"); Assert.LessOrEqual(rect.yMax, 1080f, name + " top"); } [Test] public void Battle_Renders_3D_Damage_And_Rested_Cards() { var config = new AppConfig(); config.Load(); var tokens = new TokenStore(); var api = new ApiClient(config, tokens); var player = new PlayerState(); var auth = new AuthService(api, tokens, player); var meta = new MetaService(api, player); var canvas = UiKit.CreateCanvas("Test Canvas"); var battle = new BattleView(canvas.transform, meta, () => { }); var screen = Resources.FindObjectsOfTypeAll() .First(go => go.name == "BattleScreen"); screen.SetActive(true); CardView Card(string id, int instance, bool rested = false) => new CardView { PrintingId = id, InstanceId = instance, Rested = rested, FaceUp = true, Power = 8000, BasePower = 8000, Critical = 1, BaseCritical = 1, }; var self = new PlayerView { Index = 0, Name = "Me", Vanguard = Card("BT01/001EN", 1), RearGuards = new CardView?[5], Hand = new List { Card("BT01/042EN", 2) }, Soul = new List { Card("BT01/003EN", 3) }, FaceDownDamage = 2, FaceUpDamage = new List { Card("BT01/056EN", 4) }, DeckCount = 40, Drop = new List(), }; self.RearGuards[3] = Card("BT01/042EN", 5, rested: true); var opponent = new PlayerView { Index = 1, Name = "Opponent", Vanguard = Card("BT01/001EN", 6), RearGuards = new CardView?[5], Hand = new List(), Soul = new List(), FaceDownDamage = 1, FaceUpDamage = new List(), DeckCount = 40, Drop = new List(), }; var match = new MatchView { MatchId = "render-test", You = 0, ActivePlayer = 0, Phase = GamePhase.Main, Players = new List { self, opponent }, Battle = new Vanguard.Shared.Protocol.BattleView { GuardCards = new List { Card("BT01/011EN", 7) }, }, Clock = new ClockView(), LegalIntents = new List(), }; Resources.FindObjectsOfTypeAll() .FirstOrDefault() ?.SetVisible(true); typeof(BattleView) .GetField("_view", BindingFlags.Instance | BindingFlags.NonPublic)! .SetValue(battle, match); typeof(BattleView) .GetMethod("Render", BindingFlags.Instance | BindingFlags.NonPublic)! .Invoke(battle, null); Assert.IsTrue(Exists("GameBoard3D")); var cards = Resources.FindObjectsOfTypeAll() .Where(card => card.gameObject.activeInHierarchy) .ToList(); Assert.GreaterOrEqual(cards.Count, 3); var rested = cards.FirstOrDefault(card => card.Ref.Zone == BoardZone.RearGuard && card.Ref.Player == 0 && card.Ref.Slot == 3); Assert.IsNotNull(rested, "Rested rear-guard was not rendered in 3D"); Assert.IsTrue(rested.IsRested); Assert.AreEqual(90f, Mathf.Abs(Mathf.DeltaAngle(rested.TargetYaw, 0f)), 0.1f); var damage = cards.Where(card => card.Ref.Zone == BoardZone.Damage && card.Ref.Player == 0).ToList(); Assert.AreEqual(3, damage.Count); Assert.IsFalse(damage.Single(card => card.Ref.Index == 2).IsFaceUp); Assert.AreEqual( -90f, Mathf.DeltaAngle(0f, damage.Single(card => card.Ref.Index == 0).TargetYaw), 0.1f, "Damage cards must be turned counter-clockwise"); Assert.IsTrue(cards.Any(card => card.Ref.Zone == BoardZone.Guardian && card.Ref.InstanceId == 7)); var selfZoneRoot = Resources.FindObjectsOfTypeAll() .FirstOrDefault(go => go.name == "ZoneBlocksSelf"); var opponentZoneRoot = Resources.FindObjectsOfTypeAll() .FirstOrDefault(go => go.name == "ZoneBlocksOpponent"); Assert.IsNotNull(selfZoneRoot); Assert.IsNotNull(opponentZoneRoot); var selfLabels = selfZoneRoot.GetComponentsInChildren(); var opponentLabels = opponentZoneRoot.GetComponentsInChildren(); Assert.IsTrue(selfLabels.Any(text => text.text.Contains("Рука 1"))); Assert.IsTrue(selfLabels.Any(text => text.text.Contains("Душа 1"))); Assert.IsTrue(opponentLabels.Any(text => text.text.Contains("Рука 0"))); Cleanup(); } private static bool Exists(string name) => Resources.FindObjectsOfTypeAll().Any(go => go.name == name); private static void Cleanup() { foreach (var board in Resources.FindObjectsOfTypeAll()) { if (board != null) Object.DestroyImmediate(board.gameObject); } var canvas = GameObject.Find("Test Canvas"); if (canvas != null) Object.DestroyImmediate(canvas); var eventSystem = Object.FindAnyObjectByType(); if (eventSystem != null) Object.DestroyImmediate(eventSystem.gameObject); } } }