282 lines
14 KiB
C#
282 lines
14 KiB
C#
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using NUnit.Framework;
|
|
using TKS.OutGame;
|
|
using UnityEngine;
|
|
using UnityEngine.SceneManagement;
|
|
using UnityEngine.TestTools;
|
|
using UnityEngine.UI;
|
|
|
|
namespace TKS.Tests
|
|
{
|
|
/// <summary>
|
|
/// 내정 메뉴 개편(PD 2026-11-12) — ① 탭마다 배경 장면(표 domestic_scene.csv view 줄 · 연출 = Frame 캔버스) ② 훈련 = 표 slot 줄 수만큼 병사 · 동작이 Frame 캔버스 안 · 구령 단계가 돈다
|
|
/// ③ 순찰 = 「시간당 수확」은 닫혀 있다가 누르면 펼침 · 버튼은 「수확」 「즉시 수확」 2개만(광고 버튼 없음) · 수확 → 팝업에서 광고 2배 고름 ④ 개발 = 두루마리 위 노드 전부.
|
|
/// 기대 수(병사 자리)는 표에서 읽는다(C45).
|
|
/// </summary>
|
|
public class DomesticSceneTests
|
|
{
|
|
OutGameRoot og;
|
|
|
|
[TearDown]
|
|
public void TearDown()
|
|
{
|
|
SaveStore.UseMemory(false);
|
|
}
|
|
|
|
IEnumerator Boot()
|
|
{
|
|
WorldProgress.Reset();
|
|
SaveStore.UseMemory(true);
|
|
SceneManager.LoadScene("Battle");
|
|
yield return null;
|
|
yield return null;
|
|
og = OutGameRoot.Instance;
|
|
Assert.IsNotNull(og);
|
|
og.OnTab(2);
|
|
Assert.AreEqual(og.Domestic, og.Current);
|
|
}
|
|
|
|
static void AssertFrame(Transform t, string what)
|
|
{
|
|
var c = t.GetComponentInParent<Canvas>(true);
|
|
Assert.IsNotNull(c, what);
|
|
var tag = c.GetComponent<UiLayerInfo>();
|
|
Assert.IsTrue(tag != null && tag.rate == UiRate.Frame, what + ": 매 프레임 요소가 Frame 캔버스 밖(" + c.name + ")");
|
|
}
|
|
|
|
static List<string> ButtonNames(Transform root)
|
|
{
|
|
var l = new List<string>();
|
|
foreach (var b in root.GetComponentsInChildren<Button>(false)) l.Add(b.name);
|
|
return l;
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator Training_SceneSoldiersDrillInFrameCanvas()
|
|
{
|
|
yield return Boot();
|
|
var dom = og.Domestic;
|
|
dom.SetPage(0);
|
|
yield return null;
|
|
var rows = DomesticScene.Page("train");
|
|
var view = DomesticScene.First(rows, "view");
|
|
Assert.IsNotNull(view, "표 train_view 줄 없음");
|
|
if (UISkin.Icon(view.sprite) == null) Assert.Ignore("테마에 연병장 배경 없음(" + view.sprite + ")");
|
|
var amb = dom.SceneOf(0);
|
|
Assert.IsNotNull(amb, "훈련 배경 장면 층 없음");
|
|
AssertFrame(amb.transform, "훈련 장면 층");
|
|
Assert.AreEqual(DomesticScene.All(rows, "slot").Count, dom.SoldierCount, "병사 수 = 표 train_slot 줄 수");
|
|
var drills = amb.GetComponentsInChildren<UiDrillAnim>(true);
|
|
Assert.AreEqual(dom.SoldierCount, drills.Length, "병사마다 훈련 동작");
|
|
foreach (var d in drills) AssertFrame(d.transform, "병사 " + d.name);
|
|
// 풀·꽃 흔들림(PD 2026-11-12) = 표 train_sway 줄 수 · 아래끝 고정 · Frame 캔버스
|
|
int sway = 0;
|
|
foreach (var w in amb.GetComponentsInChildren<UiWaveImage>(true))
|
|
if (w.fromBottom)
|
|
{
|
|
sway++;
|
|
AssertFrame(w.transform, "풀 흔들림 " + w.name);
|
|
}
|
|
Assert.AreEqual(DomesticScene.All(rows, "sway").Count, sway, "풀·꽃 흔들림 수 = 표");
|
|
// 구령 1회(대기 → 자세 → 모으기 → 타격 → 유지)가 실제로 돈다
|
|
var drill = DomesticScene.First(rows, "drill");
|
|
float cycle = drill.rest + drill.stand + drill.ready + drill.hit + drill.hold;
|
|
var seen = new HashSet<int>();
|
|
float t0 = Time.unscaledTime;
|
|
while (Time.unscaledTime - t0 < cycle + 0.5f)
|
|
{
|
|
seen.Add(drills[0].Stage);
|
|
yield return null;
|
|
}
|
|
Assert.IsTrue(seen.Contains(0) && seen.Contains(1) && seen.Contains(4), "동작 단계 " + string.Join(",", new List<int>(seen).ConvertAll(x => x.ToString()).ToArray()));
|
|
// 병종 탭 이름 = 게임 공통 이름(PD 2026-11-12 「보병, 창병, 기병, 궁병, 승병」 · 승병 = 게임 전체)
|
|
var types = dom.TrainTypes;
|
|
for (int i = 0; i < types.Count; i++)
|
|
{
|
|
var tab = FindActive(dom.Rt, "Type" + i);
|
|
Assert.IsNotNull(tab, "병종 탭 " + i);
|
|
Assert.AreEqual(UnitTypeInfo.Korean(types[i]), tab.GetComponentInChildren<Text>().text, "탭 글자 = 병종 이름");
|
|
}
|
|
Assert.AreEqual("승병", UnitTypeInfo.Korean(UnitType.Strategist), "책사 → 승병(게임 전체)");
|
|
// 병종을 바꾸면 그 병종 병사로(수 그대로)
|
|
dom.SetTrainType(UnitType.Cavalry);
|
|
yield return null;
|
|
Assert.AreEqual(DomesticScene.All(rows, "slot").Count, dom.SoldierCount, "기병 병사 수");
|
|
// 궁병 = 훈련 전용 그림 + 타격 때 화살(표 train_shot · PD 2026-11-12 「궁병 다시 제작」)
|
|
dom.SetTrainType(UnitType.Archer);
|
|
yield return null;
|
|
var archers = amb.GetComponentsInChildren<UiDrillAnim>(true);
|
|
Assert.AreEqual(dom.SoldierCount, archers.Length, "궁병 병사마다 훈련 동작");
|
|
bool hasShot = false;
|
|
foreach (var r in DomesticScene.All(rows, "shot")) hasShot |= UISkin.Icon(r.Sprites.Length > 1 ? r.Sprites[1] : "") != null;
|
|
if (hasShot)
|
|
{
|
|
bool flew = false;
|
|
float t1 = Time.unscaledTime;
|
|
while (Time.unscaledTime - t1 < cycle + 0.5f && !flew)
|
|
{
|
|
foreach (var a in archers) flew |= a.ShotFlying;
|
|
yield return null;
|
|
}
|
|
Assert.IsTrue(flew, "궁병 타격 때 화살이 날지 않음");
|
|
}
|
|
// 다른 탭으로 가면 훈련 장면은 숨김
|
|
dom.SetPage(1);
|
|
yield return null;
|
|
Assert.IsFalse(amb.gameObject.activeInHierarchy, "순찰 탭에서 연병장 장면이 보임");
|
|
Assert.IsNotNull(dom.SceneOf(1), "순찰 배경 장면 층 없음");
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator Patrol_InfoPanel_TwoButtons_AdChoiceAfterHarvest()
|
|
{
|
|
yield return Boot();
|
|
var s = og.Session;
|
|
s.Save.patrol.startAt = SaveStore.Iso(s.Now.AddHours(-3));
|
|
var dom = og.Domestic;
|
|
dom.SetPage(1);
|
|
yield return null;
|
|
var page = dom.Rt;
|
|
// 정보 판(PD 2026-11-12 예시) = 드롭다운 없음 · 시간당 수확 3줄(골드 · 병사 경험 · 전리품) 늘 보임 · 시간 앞 시계 · 프레임 안 보상 칸 4
|
|
Assert.IsNull(FindActive(page, "RateHead"), "드롭다운 머리가 남아 있음");
|
|
Assert.IsNull(FindActive(page, "RateList"), "드롭다운 목록이 남아 있음");
|
|
var info = FindActive(page, "RateInfo");
|
|
Assert.IsNotNull(info, "시간당 수확 줄 없음");
|
|
Assert.AreEqual(3, info.childCount, "시간당 수확 = 골드 · 경험치 · 전리품 3줄");
|
|
// 용어 = 골드 · 경험치 · 전리품 · 값 = 「획득량 / 시간」만 오른쪽 정렬(PD 2026-11-12)
|
|
string[] terms = { "골드", "경험치", "전리품" };
|
|
for (int i = 0; i < 3; i++)
|
|
{
|
|
var rowT = info.GetChild(i);
|
|
Assert.AreEqual(terms[i], rowT.GetComponentInChildren<Text>().text, "줄 " + i + " 용어");
|
|
var v = rowT.Find("PerHour").GetComponent<Text>();
|
|
Assert.AreEqual(TextAnchor.MiddleRight, v.alignment, "획득량 / 시간 = 오른쪽 정렬");
|
|
StringAssert.EndsWith("/ 시간</size>", v.text, "값 = 획득량 / 시간만");
|
|
StringAssert.DoesNotContain("출전", v.text);
|
|
}
|
|
if (UISkin.Icon("clock") != null) Assert.IsNotNull(FindActive(page, "Clock"), "시간 앞 시계 없음");
|
|
var frame = FindActive(page, "RewardFrame");
|
|
if (UISkin.Icon("dom_reward_frame") != null) Assert.IsNotNull(frame, "보상 프레임 없음");
|
|
for (int i = 0; i < 4; i++) Assert.IsNotNull(FindActive(page, "Tile" + i), "보상 칸 " + i);
|
|
var names = ButtonNames(page);
|
|
Assert.Contains("Harvest", names);
|
|
Assert.Contains("Instant", names);
|
|
Assert.IsFalse(names.Contains("Ad"), "순찰 화면에 광고 버튼이 남아 있음(PD 「수확과 즉시 수확만」)");
|
|
// 수확 → 팝업(수확 · 광고 2배 고름)
|
|
int gold0 = s.Save.gold;
|
|
FindActive(page, "Harvest").GetComponent<Button>().onClick.Invoke();
|
|
yield return null;
|
|
Assert.IsTrue(og.ModalVisible, "수확을 누르면 고르는 팝업");
|
|
var modalNames = ButtonNames(og.TopRoot);
|
|
Assert.Contains("Take", modalNames);
|
|
Assert.Contains("Ad", modalNames);
|
|
Assert.AreEqual(gold0, s.Save.gold, "팝업만 열렸는데 이미 받음");
|
|
FindActive(og.TopRoot, "Take").GetComponent<Button>().onClick.Invoke();
|
|
yield return null;
|
|
Assert.Greater(s.Save.gold, gold0, "수확 = 골드 받음");
|
|
og.CloseModal();
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator Dev_TreeOnScroll()
|
|
{
|
|
yield return Boot();
|
|
var dom = og.Domestic;
|
|
dom.SetPage(2);
|
|
yield return null;
|
|
Assert.IsNotNull(dom.SceneOf(2), "개발 배경 장면 층 없음");
|
|
Assert.IsNotNull(FindActive(dom.Rt, "Scroll"), "두루마리 없음");
|
|
Assert.AreEqual(og.Session.Db.Dev.InBranch(dom.Branch).Count, dom.NodeCount, "노드 전부");
|
|
// 노드 그림 아이콘(테마 dev_<node_id> · PD 2026-11-12) — 테마에 있으면 모든 노드가 그림
|
|
if (UISkin.Icon("dev_" + og.Session.Db.Dev.InBranch(dom.Branch)[0].id) != null)
|
|
Assert.AreEqual(dom.NodeCount, dom.IconNodeCount, "노드마다 그림 아이콘");
|
|
Assert.IsNotNull(FindActive(FindActive(dom.Rt, "Scroll"), "Paper"), "노드는 두루마리 종이 안");
|
|
}
|
|
|
|
/// <summary>
|
|
/// 영웅 파견(PD 2026-11-12) — 훈련 지휘관(그 병종만) · 순찰(문 앞 + 창병 2 + NPC) · 개발(왼쪽 아래) · 한 영웅 = 한 자리(옮기면 앞 자리 비움) · 해제.
|
|
/// 창병·NPC 수 = 표 domestic_scene.csv patrol_guard · patrol_npc 줄 수(C45).
|
|
/// </summary>
|
|
[UnityTest]
|
|
public IEnumerator Dispatch_CommanderGateDev_OneSlotPerHero()
|
|
{
|
|
yield return Boot();
|
|
var s = og.Session;
|
|
var db = s.Db;
|
|
var d = s.Save;
|
|
var dom = og.Domestic;
|
|
HeroDb.Hero any = null;
|
|
foreach (var hs in d.heroes)
|
|
{
|
|
var h = db.Heroes.Get(hs.id);
|
|
if (h != null && !h.isSoldier)
|
|
{
|
|
any = h;
|
|
break;
|
|
}
|
|
}
|
|
if (any == null) Assert.Ignore("보유 영웅 없음");
|
|
string train = DispatchRules.TrainSlot(any.type);
|
|
// 훈련 = 그 병종 영웅만
|
|
foreach (UnitType t in System.Enum.GetValues(typeof(UnitType)))
|
|
if (t != any.type && DomesticScreenHasType(dom, t))
|
|
{
|
|
Assert.IsNotNull(DispatchRules.Assign(db, d, DispatchRules.TrainSlot(t), any.id), "다른 병종 지휘관이 됨");
|
|
break;
|
|
}
|
|
Assert.IsNull(s.Do(() => DispatchRules.Assign(db, d, train, any.id)));
|
|
dom.SetTrainType(any.type);
|
|
dom.SetPage(0);
|
|
yield return null;
|
|
Assert.IsTrue(dom.CommanderShown, "훈련 장면에 지휘관 없음");
|
|
// 순찰로 옮기면 훈련 자리는 빔
|
|
Assert.IsNull(s.Do(() => DispatchRules.Assign(db, d, DispatchRules.Patrol, any.id)));
|
|
Assert.AreEqual("", DispatchRules.Get(d, train), "옮긴 뒤 앞 자리가 남음");
|
|
dom.SetPage(1);
|
|
yield return null;
|
|
var rows = DomesticScene.Page("patrol");
|
|
Assert.IsTrue(dom.PatrolHeroShown, "문 앞 순찰 영웅 없음");
|
|
Assert.AreEqual(DomesticScene.All(rows, "guard").Count, dom.GuardCount, "문 앞 창병 수 = 표");
|
|
Assert.AreEqual(DomesticScene.All(rows, "npc").Count, dom.NpcCount, "상인·주민 NPC 수 = 표");
|
|
int npcAnim = 0;
|
|
foreach (var fa in dom.SceneOf(1).GetComponentsInChildren<UiFrameAnim>(true))
|
|
if (fa.name.StartsWith("Npc_")) npcAnim++;
|
|
Assert.AreEqual(dom.NpcCount, npcAnim, "NPC 마다 대기 숨 고르기(유닛과 같은 R22 클립)");
|
|
foreach (var fa in dom.SceneOf(1).GetComponentsInChildren<UiFrameAnim>(true))
|
|
{
|
|
var c = fa.GetComponentInParent<Canvas>(true).GetComponent<UiLayerInfo>();
|
|
Assert.IsTrue(c != null && c.rate == UiRate.Frame, "순찰 대기 숨 고르기가 Frame 캔버스 밖");
|
|
}
|
|
// 고르기 창 = 보유 영웅 후보
|
|
dom.OpenDispatch(DispatchRules.Patrol);
|
|
yield return null;
|
|
Assert.IsTrue(og.ModalVisible, "파견 고르기 창");
|
|
Assert.AreEqual(DispatchRules.Candidates(db, d, DispatchRules.Patrol).Count, dom.DispatchChoices);
|
|
og.CloseModal();
|
|
// 개발로 옮김 → 개발 탭 왼쪽 아래 영웅 · 순찰은 빔
|
|
Assert.IsNull(s.Do(() => DispatchRules.Assign(db, d, DispatchRules.Dev, any.id)));
|
|
Assert.AreEqual("", DispatchRules.Get(d, DispatchRules.Patrol));
|
|
dom.SetPage(2);
|
|
yield return null;
|
|
Assert.IsTrue(dom.DevHeroShown, "개발 배치 영웅 없음");
|
|
s.Do(() => DispatchRules.Clear(d, DispatchRules.Dev));
|
|
yield return null;
|
|
Assert.IsFalse(dom.DevHeroShown, "해제 뒤에도 영웅이 남음");
|
|
}
|
|
|
|
static bool DomesticScreenHasType(DomesticScreen dom, UnitType t)
|
|
{
|
|
return dom.TrainTypes.Contains(t);
|
|
}
|
|
|
|
static Transform FindActive(Transform root, string name)
|
|
{
|
|
foreach (var t in root.GetComponentsInChildren<Transform>(false))
|
|
if (t.name == name)
|
|
return t;
|
|
return null;
|
|
}
|
|
}
|
|
}
|