using System.Collections.Generic;
using NUnit.Framework;
using BaseGames.Core;
using BaseGames.Core.Events;
namespace BaseGames.Tests.EditMode
{
///
/// GameStateMachine + GameStateId 单元测试(EditMode)。
/// 覆盖:状态注册、合法/非法转换、OnEnter/OnExit 回调顺序、Tick 分发。
///
[TestFixture]
public class GameStateMachineTests
{
// ── Mock 状态实现 ────────────────────────────────────────────────────
private class MockState : IGameState
{
public GameStateId Id { get; }
public IReadOnlyCollection ValidNextStates { get; }
public bool OnEnterCalled { get; private set; }
public bool OnExitCalled { get; private set; }
public GameStateId EnteredFrom { get; private set; }
public GameStateId ExitedTo { get; private set; }
public int TickCount { get; private set; }
public MockState(GameStateId id, params GameStateId[] validNext)
{
Id = id;
ValidNextStates = new HashSet(validNext);
}
public void OnEnter(GameStateId from) { OnEnterCalled = true; EnteredFrom = from; }
public void OnExit(GameStateId to) { OnExitCalled = true; ExitedTo = to; }
public void Tick(float dt) => TickCount++;
}
private static readonly GameStateId StateA = new GameStateId("StateA");
private static readonly GameStateId StateB = new GameStateId("StateB");
private static readonly GameStateId StateC = new GameStateId("StateC");
// ── 初始状态 ─────────────────────────────────────────────────────────
[Test]
public void GameStateMachine_InitialState_IsDefault()
{
var fsm = new GameStateMachine();
Assert.AreEqual(default(GameStateId), fsm.CurrentStateId,
"未注册任何状态时,CurrentStateId 应为 default");
}
// ── 状态注册与转换 ────────────────────────────────────────────────────
[Test]
public void TransitionTo_UnknownState_ReturnsFalse()
{
var fsm = new GameStateMachine();
bool result = fsm.TransitionTo(StateA, out string error);
Assert.IsFalse(result, "未注册的状态转换应返回 false");
StringAssert.Contains("未知状态", error);
}
[Test]
public void TransitionTo_RegisteredState_FromNull_Succeeds()
{
var fsm = new GameStateMachine();
var stateA = new MockState(StateA);
fsm.Register(stateA);
bool result = fsm.TransitionTo(StateA, out string error);
Assert.IsTrue(result, "注册状态后首次转换应成功");
Assert.IsNull(error, "成功转换时 error 应为 null");
Assert.AreEqual(StateA, fsm.CurrentStateId);
}
[Test]
public void TransitionTo_ValidNextState_Succeeds()
{
var fsm = new GameStateMachine();
var stateA = new MockState(StateA, StateB); // A 可以转换到 B
var stateB = new MockState(StateB);
fsm.Register(stateA);
fsm.Register(stateB);
fsm.TransitionTo(StateA, out _);
bool result = fsm.TransitionTo(StateB, out string error);
Assert.IsTrue(result);
Assert.IsNull(error);
Assert.AreEqual(StateB, fsm.CurrentStateId);
}
[Test]
public void TransitionTo_InvalidNextState_ReturnsFalse()
{
var fsm = new GameStateMachine();
var stateA = new MockState(StateA); // A 无有效下一状态
var stateB = new MockState(StateB);
fsm.Register(stateA);
fsm.Register(stateB);
fsm.TransitionTo(StateA, out _);
bool result = fsm.TransitionTo(StateB, out string error);
Assert.IsFalse(result, "非法转换应返回 false");
StringAssert.Contains("非法转换", error);
Assert.AreEqual(StateA, fsm.CurrentStateId, "非法转换后状态不应改变");
}
// ── OnEnter / OnExit 回调 ────────────────────────────────────────────
[Test]
public void TransitionTo_CallsOnExit_OnPreviousState()
{
var fsm = new GameStateMachine();
var stateA = new MockState(StateA, StateB);
var stateB = new MockState(StateB);
fsm.Register(stateA);
fsm.Register(stateB);
fsm.TransitionTo(StateA, out _);
fsm.TransitionTo(StateB, out _);
Assert.IsTrue(stateA.OnExitCalled, "转出 StateA 时应调用 OnExit");
Assert.AreEqual(StateB, stateA.ExitedTo, "OnExit 传入的下一状态应为 StateB");
}
[Test]
public void TransitionTo_CallsOnEnter_OnNextState()
{
var fsm = new GameStateMachine();
var stateA = new MockState(StateA, StateB);
var stateB = new MockState(StateB);
fsm.Register(stateA);
fsm.Register(stateB);
fsm.TransitionTo(StateA, out _);
fsm.TransitionTo(StateB, out _);
Assert.IsTrue(stateB.OnEnterCalled, "转入 StateB 时应调用 OnEnter");
Assert.AreEqual(StateA, stateB.EnteredFrom, "OnEnter 传入的前一状态应为 StateA");
}
[Test]
public void TransitionTo_FirstEntry_OnEnterFromIsDefault()
{
var fsm = new GameStateMachine();
var stateA = new MockState(StateA);
fsm.Register(stateA);
fsm.TransitionTo(StateA, out _);
Assert.AreEqual(default(GameStateId), stateA.EnteredFrom,
"首次进入状态时,OnEnter 的 from 参数应为 default");
}
// ── Tick ──────────────────────────────────────────────────────────────
[Test]
public void Tick_DelegatesTo_CurrentState()
{
var fsm = new GameStateMachine();
var stateA = new MockState(StateA);
fsm.Register(stateA);
fsm.TransitionTo(StateA, out _);
fsm.Tick(0.016f);
fsm.Tick(0.016f);
Assert.AreEqual(2, stateA.TickCount, "Tick 应转发给当前状态");
}
[Test]
public void Tick_NoCurrentState_DoesNotThrow()
{
var fsm = new GameStateMachine();
Assert.DoesNotThrow(() => fsm.Tick(0.016f));
}
// ── 重复注册 ─────────────────────────────────────────────────────────
[Test]
public void Register_SameId_OverwritesPreviousState()
{
var fsm = new GameStateMachine();
var stateA1 = new MockState(StateA);
var stateA2 = new MockState(StateA, StateB); // 重新注册,含 B 作为有效下一状态
fsm.Register(stateA1);
fsm.Register(stateA2);
fsm.TransitionTo(StateA, out _);
var stateB = new MockState(StateB);
fsm.Register(stateB);
bool result = fsm.TransitionTo(StateB, out _);
Assert.IsTrue(result, "重新注册后的状态(含 ValidNextStates)应覆盖旧注册");
}
// ── GameStateId 值相等性 ───────────────────────────────────────────────
[Test]
public void GameStateId_SameKey_AreEqual()
{
var id1 = new GameStateId("TestState");
var id2 = new GameStateId("TestState");
Assert.AreEqual(id1, id2, "相同 key 的 GameStateId 应相等");
}
[Test]
public void GameStateId_DifferentKey_AreNotEqual()
{
var id1 = new GameStateId("StateA");
var id2 = new GameStateId("StateB");
Assert.AreNotEqual(id1, id2);
}
[Test]
public void GameStateId_DefaultIsNotEqualToNamed()
{
var named = new GameStateId("Something");
var defaultId = default(GameStateId);
Assert.AreNotEqual(named, defaultId);
}
// ── BuiltinGameStates 唯一性 ───────────────────────────────────────────
[Test]
public void BuiltinStates_AllIdsAreUnique()
{
var ids = new[]
{
GameStates.Initializing,
GameStates.MainMenu,
GameStates.Gameplay,
GameStates.Paused,
GameStates.BossFight,
GameStates.Cutscene,
GameStates.LoadingScene,
GameStates.Dead,
GameStates.GameOver,
};
var set = new HashSet(ids);
Assert.AreEqual(ids.Length, set.Count, "9 个内置游戏状态 ID 必须全部唯一");
}
[Test]
public void BuiltinStates_NoneIsDefault()
{
var ids = new[]
{
GameStates.Initializing,
GameStates.MainMenu,
GameStates.Gameplay,
GameStates.Paused,
GameStates.BossFight,
GameStates.Cutscene,
GameStates.LoadingScene,
GameStates.Dead,
GameStates.GameOver,
};
foreach (var id in ids)
{
Assert.AreNotEqual(default(GameStateId), id,
$"内置状态 {id} 不应等于 default(GameStateId)");
}
}
}
}