using System.Collections.Generic; using System.Linq; using NUnit.Framework; using UnityEngine; using BaseGames.Editor.Modules; using BaseGames.Enemies.Abilities; namespace BaseGames.Tests.EditMode.EditorTools { /// /// 能力总览表格的取数与排序。UI 构建不可测,所以把「一行长什么样」「怎么排序」 /// 「问题从哪来」抽成纯函数放这里——表格真正会出错的也正是这三处。 /// public class EnemyAbilityOverviewTests { private readonly List _created = new List(); [TearDown] public void TearDown() { // 项目关闭了 Domain/Scene Reload:断言失败会跳过用例后续语句, // 清理必须放在 TearDown,否则失败一次就往内存里漏一个 SO。 foreach (var so in _created) if (so != null) Object.DestroyImmediate(so); _created.Clear(); } private EnemyAbilitySO MakeAbility( string id, AbilityCategory category = AbilityCategory.None, float range = 1f, float weight = 1f, float cooldown = 1f, int priority = 0, string exclusion = "") { var so = ScriptableObject.CreateInstance(); so.abilityId = id; so.category = category; so.rangeRadius = range; so.weight = weight; so.cooldown = cooldown; so.priority = priority; so.exclusionGroup = exclusion; _created.Add(so); return so; } // ── 归属提取 ───────────────────────────────────────────────────── [Test] public void OwnerFromPath_TakesEnemyFolderName() { // 能力资产按 AssetFolderSpec 落在 Data/Enemies/{敌人}/Abilities/ 下, // 归属列靠路径推导——表格要横向对比,必须能一眼看出这招是谁的。 Assert.AreEqual("ChaoFeng", EnemyAbilityOverview.OwnerFromPath( "Assets/_Game/Data/Enemies/ChaoFeng/Abilities/ABL_ChaoFeng_Intro.asset")); Assert.AreEqual("E004", EnemyAbilityOverview.OwnerFromPath( "Assets/_Game/Data/Enemies/E004/Abilities/ABL_E004_Bite.asset")); } [Test] public void OwnerFromPath_OffSpecLayout_ReturnsPlaceholder() { // 放错目录的资产不该让整张表崩掉,但也不能假装它有归属——显示占位符, // 让"这个资产没按规范放"本身在表里可见。 Assert.AreEqual(EnemyAbilityOverview.UnknownOwner, EnemyAbilityOverview.OwnerFromPath("Assets/Random/ABL_Stray.asset")); Assert.AreEqual(EnemyAbilityOverview.UnknownOwner, EnemyAbilityOverview.OwnerFromPath("")); } // ── 行取数 ─────────────────────────────────────────────────────── [Test] public void BuildRow_CarriesTunableFields() { var so = MakeAbility("blink_strike", AbilityCategory.Attack, range: 3.5f, weight: 2f, cooldown: 4.25f, priority: 7, exclusion: "melee"); var row = EnemyAbilityOverview.BuildRow( so, "Assets/_Game/Data/Enemies/E004/Abilities/ABL_E004_Blink.asset"); Assert.AreEqual("E004", row.Owner); Assert.AreEqual("blink_strike", row.Id); Assert.AreEqual(AbilityCategory.Attack, row.Category); Assert.AreEqual(3.5f, row.Range); Assert.AreEqual(2f, row.Weight); Assert.AreEqual(4.25f, row.Cooldown); Assert.AreEqual(7, row.Priority); Assert.AreEqual("melee", row.Exclusion); Assert.AreSame(so, row.Asset, "行必须持有资产引用,点选才能跳到它"); } [Test] public void BuildRow_SurfacesIssue_FromAssetsOwnValidate() { // 问题判定不在表格里另写一份规则,而是调资产自己的 Validate()。 // 这条断言守住"规则只有一处"——否则 SO 侧改了规则,表格会继续按旧规则报。 var broken = MakeAbility("bad_attack", AbilityCategory.Attack, range: 0f); var ok = MakeAbility("good_attack", AbilityCategory.Attack, range: 2f); var brokenRow = EnemyAbilityOverview.BuildRow(broken, "Assets/x/ABL_A.asset"); var okRow = EnemyAbilityOverview.BuildRow(ok, "Assets/x/ABL_B.asset"); Assert.IsTrue(brokenRow.HasIssue, "Attack 类但射程为 0 的招永远选不中,表格必须标出来"); Assert.IsFalse(string.IsNullOrEmpty(brokenRow.Issue), "要带上原因文本,不能只给个红点"); Assert.IsFalse(okRow.HasIssue); } // ── 排序 ───────────────────────────────────────────────────────── [Test] public void Sort_ByCooldown_BothDirections() { var rows = new List { EnemyAbilityOverview.BuildRow(MakeAbility("b", cooldown: 5f), "Assets/x/B.asset"), EnemyAbilityOverview.BuildRow(MakeAbility("a", cooldown: 1f), "Assets/x/A.asset"), EnemyAbilityOverview.BuildRow(MakeAbility("c", cooldown: 3f), "Assets/x/C.asset"), }; var asc = EnemyAbilityOverview.Sort(rows, AbilityOverviewColumn.Cooldown, ascending: true); CollectionAssert.AreEqual(new[] { "a", "c", "b" }, asc.Select(r => r.Id).ToArray()); var desc = EnemyAbilityOverview.Sort(rows, AbilityOverviewColumn.Cooldown, ascending: false); CollectionAssert.AreEqual(new[] { "b", "c", "a" }, desc.Select(r => r.Id).ToArray()); } [Test] public void Sort_ByOwnerThenId_IsStableAcrossEnemies() { // 默认视图按归属分组才有对比意义:同一敌人的招要挨在一起,组内按 id 稳定排列。 var rows = new List { EnemyAbilityOverview.BuildRow(MakeAbility("bite"), "Assets/_Game/Data/Enemies/E005/Abilities/ABL_1.asset"), EnemyAbilityOverview.BuildRow(MakeAbility("acid"), "Assets/_Game/Data/Enemies/E004/Abilities/ABL_2.asset"), EnemyAbilityOverview.BuildRow(MakeAbility("appear"),"Assets/_Game/Data/Enemies/E004/Abilities/ABL_3.asset"), }; var sorted = EnemyAbilityOverview.Sort(rows, AbilityOverviewColumn.Owner, ascending: true); CollectionAssert.AreEqual( new[] { "acid", "appear", "bite" }, sorted.Select(r => r.Id).ToArray(), "同归属内按 id 排,跨归属按归属名排"); } } }