mirror of
https://github.com/Earth-Genesis-Games/Ajedrez_Purgatorio.git
synced 2026-09-11 09:47:35 +00:00
- Duel flow: mating piece vs boss while board collapses (PurgatoryDuelManager/Rules/UI/MuerteQuotes) - GameState.PurgatoryDuel + CampaignManager rescue/restart hooks + SquareClick routing - Dice-based rescue of most valuable dead identity; final defeat restarts Chapter 1 - 6 SFX clips wired in Ch1/2/3 via AudioClipAssigner - OptionsMenuController runtime audio/graphics panel, wired to MainMenu - Tests migrated to Assets/Tests/EditMode subfolders + PurgatoryDuelRulesTests (200 total) - Editor tools: DiagAssign, SpeakerDatabaseSetup, PurgatoryCanvasMaintenance
539 lines
18 KiB
C#
539 lines
18 KiB
C#
using NUnit.Framework;
|
|
using UnityEngine;
|
|
using System.Collections.Generic;
|
|
|
|
namespace AjedrezPurgatorio.Tests.Unit.Dialogue
|
|
{
|
|
/// <summary>
|
|
/// Unit tests para DialogueSystem - Sistema de branching condicional.
|
|
/// Cubre: piece_alive, pieces_lost_count, chapter_complete conditions.
|
|
/// </summary>
|
|
[TestFixture]
|
|
[Category("Unit")]
|
|
[Category("DialogueSystem")]
|
|
[Category("Branching")]
|
|
[Category("FastTests")]
|
|
public class DialogueSystemBranchingTests
|
|
{
|
|
private DialogueSystem _dialogueSystem;
|
|
private CampaignState _campaignState;
|
|
|
|
[SetUp]
|
|
public void Setup()
|
|
{
|
|
// Crear GameObject con DialogueSystem
|
|
var go = new GameObject("TestDialogueSystem");
|
|
_dialogueSystem = go.AddComponent<DialogueSystem>();
|
|
|
|
// Crear CampaignState
|
|
_campaignState = ScriptableObject.CreateInstance<CampaignState>();
|
|
|
|
// Inyectar CampaignState en DialogueSystem usando reflection
|
|
var field = typeof(DialogueSystem).GetField("_campaignState",
|
|
System.Reflection.BindingFlags.NonPublic |
|
|
System.Reflection.BindingFlags.Instance);
|
|
if (field != null)
|
|
{
|
|
field.SetValue(_dialogueSystem, _campaignState);
|
|
}
|
|
|
|
// Inyectar un diálogo vacío: GetNextNode busca nodos en
|
|
// _currentDialogue.nodes y NREaría sin esto.
|
|
var currentDialogueField = typeof(DialogueSystem).GetField("_currentDialogue",
|
|
System.Reflection.BindingFlags.NonPublic |
|
|
System.Reflection.BindingFlags.Instance);
|
|
if (currentDialogueField != null)
|
|
{
|
|
currentDialogueField.SetValue(_dialogueSystem, new DialogueData
|
|
{
|
|
id = "test_dialogue",
|
|
nodes = new List<DialogueNode>()
|
|
});
|
|
}
|
|
}
|
|
|
|
[TearDown]
|
|
public void Teardown()
|
|
{
|
|
if (_dialogueSystem != null)
|
|
{
|
|
Object.DestroyImmediate(_dialogueSystem.gameObject);
|
|
}
|
|
if (_campaignState != null)
|
|
{
|
|
Object.DestroyImmediate(_campaignState);
|
|
}
|
|
}
|
|
|
|
#region piece_alive Condition Tests
|
|
|
|
[Test]
|
|
public void CheckPieceAlive_WhenAlive_ReturnsTrue()
|
|
{
|
|
// Arrange
|
|
_campaignState.RegisterPieceAlive("elena");
|
|
|
|
// Act
|
|
bool result = InvokeCheckCondition("piece_alive", "elena");
|
|
|
|
// Assert
|
|
Assert.IsTrue(result, "CheckCondition debería retornar true para pieza viva");
|
|
}
|
|
|
|
[Test]
|
|
public void CheckPieceAlive_WhenDead_ReturnsFalse()
|
|
{
|
|
// Arrange
|
|
_campaignState.RegisterPieceAlive("elena");
|
|
_campaignState.RegisterPieceLost("elena");
|
|
|
|
// Act
|
|
bool result = InvokeCheckCondition("piece_alive", "elena");
|
|
|
|
// Assert
|
|
Assert.IsFalse(result, "CheckCondition debería retornar false para pieza muerta");
|
|
}
|
|
|
|
[Test]
|
|
[TestCase("Elena")]
|
|
[TestCase("ELENA")]
|
|
[TestCase("elena")]
|
|
[TestCase("ElEnA")]
|
|
public void CheckPieceAlive_CaseInsensitive(string pieceName)
|
|
{
|
|
// Arrange
|
|
_campaignState.RegisterPieceAlive("elena");
|
|
|
|
// Act
|
|
bool result = InvokeCheckCondition("piece_alive", pieceName);
|
|
|
|
// Assert
|
|
Assert.IsTrue(result, $"CheckPieceAlive debería ser case-insensitive para '{pieceName}'");
|
|
}
|
|
|
|
[Test]
|
|
public void EvaluateBranching_PieceAlive_SelectsCorrectNode()
|
|
{
|
|
// Arrange
|
|
_campaignState.RegisterPieceAlive("elena");
|
|
|
|
var currentNode = new DialogueNode
|
|
{
|
|
id = "test_node",
|
|
conditions = new List<DialogueCondition>
|
|
{
|
|
new DialogueCondition
|
|
{
|
|
type = "piece_alive",
|
|
param = "elena",
|
|
nextNode = "elena_alive_node"
|
|
}
|
|
},
|
|
next = "default_node"
|
|
};
|
|
|
|
// Act
|
|
string nextNodeId = InvokeEvaluateBranching(currentNode);
|
|
|
|
// Assert
|
|
Assert.AreEqual("elena_alive_node", nextNodeId, "Debería seleccionar nodo de Elena viva");
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region pieces_lost_count Condition Tests
|
|
|
|
[Test]
|
|
[TestCase("<3", 2, true)]
|
|
[TestCase("<3", 3, false)]
|
|
[TestCase("<3", 4, false)]
|
|
public void CheckPiecesLostCount_LessThan(string param, int piecesLost, bool expected)
|
|
{
|
|
// Arrange
|
|
SetupPiecesLost(piecesLost);
|
|
|
|
// Act
|
|
bool result = InvokeCheckCondition("pieces_lost_count", param);
|
|
|
|
// Assert
|
|
Assert.AreEqual(expected, result, $"pieces_lost_count {param} con {piecesLost} pérdidas debería ser {expected}");
|
|
}
|
|
|
|
[Test]
|
|
[TestCase("<=3", 2, true)]
|
|
[TestCase("<=3", 3, true)]
|
|
[TestCase("<=3", 4, false)]
|
|
public void CheckPiecesLostCount_LessOrEqual(string param, int piecesLost, bool expected)
|
|
{
|
|
// Arrange
|
|
SetupPiecesLost(piecesLost);
|
|
|
|
// Act
|
|
bool result = InvokeCheckCondition("pieces_lost_count", param);
|
|
|
|
// Assert
|
|
Assert.AreEqual(expected, result, $"pieces_lost_count {param} con {piecesLost} pérdidas debería ser {expected}");
|
|
}
|
|
|
|
[Test]
|
|
[TestCase(">3", 4, true)]
|
|
[TestCase(">3", 3, false)]
|
|
[TestCase(">3", 2, false)]
|
|
public void CheckPiecesLostCount_GreaterThan(string param, int piecesLost, bool expected)
|
|
{
|
|
// Arrange
|
|
SetupPiecesLost(piecesLost);
|
|
|
|
// Act
|
|
bool result = InvokeCheckCondition("pieces_lost_count", param);
|
|
|
|
// Assert
|
|
Assert.AreEqual(expected, result, $"pieces_lost_count {param} con {piecesLost} pérdidas debería ser {expected}");
|
|
}
|
|
|
|
[Test]
|
|
[TestCase(">=3", 2, false)]
|
|
[TestCase(">=3", 3, true)]
|
|
[TestCase(">=3", 4, true)]
|
|
public void CheckPiecesLostCount_GreaterOrEqual(string param, int piecesLost, bool expected)
|
|
{
|
|
// Arrange
|
|
SetupPiecesLost(piecesLost);
|
|
|
|
// Act
|
|
bool result = InvokeCheckCondition("pieces_lost_count", param);
|
|
|
|
// Assert
|
|
Assert.AreEqual(expected, result, $"pieces_lost_count {param} con {piecesLost} pérdidas debería ser {expected}");
|
|
}
|
|
|
|
[Test]
|
|
[TestCase("==3", 3, true)]
|
|
[TestCase("==3", 2, false)]
|
|
[TestCase("==3", 4, false)]
|
|
public void CheckPiecesLostCount_Equals(string param, int piecesLost, bool expected)
|
|
{
|
|
// Arrange
|
|
SetupPiecesLost(piecesLost);
|
|
|
|
// Act
|
|
bool result = InvokeCheckCondition("pieces_lost_count", param);
|
|
|
|
// Assert
|
|
Assert.AreEqual(expected, result, $"pieces_lost_count {param} con {piecesLost} pérdidas debería ser {expected}");
|
|
}
|
|
|
|
[Test]
|
|
public void EvaluateBranching_PiecesLostCount_SelectsCorrectNode()
|
|
{
|
|
// Arrange
|
|
SetupPiecesLost(2);
|
|
|
|
var currentNode = new DialogueNode
|
|
{
|
|
id = "test_node",
|
|
conditions = new List<DialogueCondition>
|
|
{
|
|
new DialogueCondition
|
|
{
|
|
type = "pieces_lost_count",
|
|
param = "<3",
|
|
nextNode = "few_losses_node"
|
|
}
|
|
},
|
|
next = "many_losses_node"
|
|
};
|
|
|
|
// Act
|
|
string nextNodeId = InvokeEvaluateBranching(currentNode);
|
|
|
|
// Assert
|
|
Assert.AreEqual("few_losses_node", nextNodeId, "Debería seleccionar nodo de pocas pérdidas");
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region chapter_complete Condition Tests
|
|
|
|
[Test]
|
|
public void CheckChapterComplete_WhenComplete_ReturnsTrue()
|
|
{
|
|
// Arrange
|
|
_campaignState.CompleteChapter("ch1_factory", wasVictory: true);
|
|
|
|
// Act
|
|
bool result = InvokeCheckCondition("chapter_complete", "ch1_factory");
|
|
|
|
// Assert
|
|
Assert.IsTrue(result, "CheckCondition debería retornar true para capítulo completado");
|
|
}
|
|
|
|
[Test]
|
|
public void CheckChapterComplete_WhenNotComplete_ReturnsFalse()
|
|
{
|
|
// Act
|
|
bool result = InvokeCheckCondition("chapter_complete", "ch1_factory");
|
|
|
|
// Assert
|
|
Assert.IsFalse(result, "CheckCondition debería retornar false para capítulo no completado");
|
|
}
|
|
|
|
[Test]
|
|
public void CheckChapterComplete_Ch3Victory_ReturnsLastChapterWasVictory()
|
|
{
|
|
// Arrange
|
|
_campaignState.CompleteChapter("ch3_court", wasVictory: true);
|
|
|
|
// Act
|
|
bool result = InvokeCheckCondition("chapter_complete", "ch3");
|
|
|
|
// Assert
|
|
Assert.IsTrue(result, "chapter_complete('ch3') debería retornar LastChapterWasVictory cuando es victoria");
|
|
}
|
|
|
|
[Test]
|
|
public void CheckChapterComplete_Ch3Defeat_ReturnsFalse()
|
|
{
|
|
// Arrange
|
|
_campaignState.CompleteChapter("ch3_court", wasVictory: false);
|
|
|
|
// Act
|
|
bool result = InvokeCheckCondition("chapter_complete", "ch3");
|
|
|
|
// Assert
|
|
Assert.IsFalse(result, "chapter_complete('ch3') debería retornar false cuando es derrota");
|
|
}
|
|
|
|
[Test]
|
|
public void EvaluateBranching_ChapterComplete_SelectsCorrectNode()
|
|
{
|
|
// Arrange
|
|
_campaignState.CompleteChapter("ch3_court", wasVictory: true);
|
|
|
|
var currentNode = new DialogueNode
|
|
{
|
|
id = "test_node",
|
|
conditions = new List<DialogueCondition>
|
|
{
|
|
new DialogueCondition
|
|
{
|
|
type = "chapter_complete",
|
|
param = "ch3",
|
|
nextNode = "victory_node"
|
|
}
|
|
},
|
|
next = "defeat_node"
|
|
};
|
|
|
|
// Act
|
|
string nextNodeId = InvokeEvaluateBranching(currentNode);
|
|
|
|
// Assert
|
|
Assert.AreEqual("victory_node", nextNodeId, "Debería seleccionar nodo de victoria");
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Edge Cases Tests
|
|
|
|
[Test]
|
|
public void EvaluateBranching_NoConditions_ReturnsDirectNext()
|
|
{
|
|
// Arrange
|
|
var currentNode = new DialogueNode
|
|
{
|
|
id = "test_node",
|
|
conditions = null,
|
|
next = "next_node"
|
|
};
|
|
RegisterNode("next_node");
|
|
|
|
// Act: GetNextNode con condiciones null salta directo a 'next'
|
|
var nextNode = InvokeGetNextNode(currentNode);
|
|
|
|
// Assert
|
|
Assert.IsNotNull(nextNode, "Debería encontrar el nodo 'next_node' en el diálogo actual");
|
|
Assert.AreEqual("next_node", nextNode.id, "Sin condiciones debería retornar directamente 'next'");
|
|
}
|
|
|
|
[Test]
|
|
public void EvaluateBranching_EmptyConditions_ReturnsDirectNext()
|
|
{
|
|
// Arrange
|
|
var currentNode = new DialogueNode
|
|
{
|
|
id = "test_node",
|
|
conditions = new List<DialogueCondition>(),
|
|
next = "next_node"
|
|
};
|
|
RegisterNode("next_node");
|
|
|
|
// Act: GetNextNode con lista vacía salta directo a 'next'
|
|
var nextNode = InvokeGetNextNode(currentNode);
|
|
|
|
// Assert
|
|
Assert.IsNotNull(nextNode, "Debería encontrar el nodo 'next_node' en el diálogo actual");
|
|
Assert.AreEqual("next_node", nextNode.id, "Condiciones vacías debería retornar directamente 'next'");
|
|
}
|
|
|
|
[Test]
|
|
public void EvaluateBranching_MultipleConditions_FirstMatchWins()
|
|
{
|
|
// Arrange
|
|
_campaignState.RegisterPieceAlive("elena");
|
|
_campaignState.RegisterPieceAlive("ricardo");
|
|
|
|
var currentNode = new DialogueNode
|
|
{
|
|
id = "test_node",
|
|
conditions = new List<DialogueCondition>
|
|
{
|
|
new DialogueCondition
|
|
{
|
|
type = "piece_alive",
|
|
param = "elena",
|
|
nextNode = "elena_node"
|
|
},
|
|
new DialogueCondition
|
|
{
|
|
type = "piece_alive",
|
|
param = "ricardo",
|
|
nextNode = "ricardo_node"
|
|
}
|
|
},
|
|
next = "default_node"
|
|
};
|
|
|
|
// Act
|
|
string nextNodeId = InvokeEvaluateBranching(currentNode);
|
|
|
|
// Assert
|
|
Assert.AreEqual("elena_node", nextNodeId, "Primera condición que cumple debería ganar");
|
|
}
|
|
|
|
[Test]
|
|
public void EvaluateBranching_NoMatchingCondition_ReturnsDefault()
|
|
{
|
|
// Arrange (Elena NO está viva)
|
|
var currentNode = new DialogueNode
|
|
{
|
|
id = "test_node",
|
|
conditions = new List<DialogueCondition>
|
|
{
|
|
new DialogueCondition
|
|
{
|
|
type = "piece_alive",
|
|
param = "elena",
|
|
nextNode = "elena_node"
|
|
}
|
|
},
|
|
next = "default_node"
|
|
};
|
|
|
|
// Act
|
|
string nextNodeId = InvokeEvaluateBranching(currentNode);
|
|
|
|
// Assert
|
|
Assert.AreEqual("default_node", nextNodeId, "Sin condiciones que cumplan debería retornar default");
|
|
}
|
|
|
|
[Test]
|
|
public void CheckCondition_UnknownType_ReturnsDefault()
|
|
{
|
|
// Act
|
|
bool result = InvokeCheckCondition("unknown_type", "some_param");
|
|
|
|
// Assert
|
|
Assert.IsFalse(result, "Tipo de condición desconocido debería retornar false");
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Helper Methods
|
|
|
|
/// <summary>
|
|
/// Configura el CampaignState para tener X piezas perdidas.
|
|
/// </summary>
|
|
private void SetupPiecesLost(int count)
|
|
{
|
|
_campaignState.StartChapter("test_chapter");
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
_campaignState.RegisterPieceLost($"piece_{i}");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Invoca el método privado CheckCondition usando reflection.
|
|
/// </summary>
|
|
private bool InvokeCheckCondition(string type, string param)
|
|
{
|
|
var method = typeof(DialogueSystem).GetMethod("CheckCondition",
|
|
System.Reflection.BindingFlags.NonPublic |
|
|
System.Reflection.BindingFlags.Instance);
|
|
|
|
if (method == null)
|
|
{
|
|
Assert.Fail("Método CheckCondition no encontrado");
|
|
return false;
|
|
}
|
|
|
|
var condition = new DialogueCondition { type = type, param = param };
|
|
return (bool)method.Invoke(_dialogueSystem, new object[] { condition });
|
|
}
|
|
|
|
/// <summary>
|
|
/// Invoca el método privado GetNextNode usando reflection.
|
|
/// Devuelve el nodo resultante (firma real: DialogueNode, no string).
|
|
/// Requiere que el nodo destino exista en _currentDialogue.nodes.
|
|
/// </summary>
|
|
private DialogueNode InvokeGetNextNode(DialogueNode node)
|
|
{
|
|
var method = typeof(DialogueSystem).GetMethod("GetNextNode",
|
|
System.Reflection.BindingFlags.NonPublic |
|
|
System.Reflection.BindingFlags.Instance);
|
|
|
|
if (method == null)
|
|
{
|
|
Assert.Fail("Método GetNextNode no encontrado");
|
|
return null;
|
|
}
|
|
|
|
return (DialogueNode)method.Invoke(_dialogueSystem, new object[] { node });
|
|
}
|
|
|
|
/// <summary>
|
|
/// Invoca el método privado EvaluateBranching usando reflection.
|
|
/// Devuelve el ID del siguiente nodo según las condiciones.
|
|
/// </summary>
|
|
private string InvokeEvaluateBranching(DialogueNode node)
|
|
{
|
|
var method = typeof(DialogueSystem).GetMethod("EvaluateBranching",
|
|
System.Reflection.BindingFlags.NonPublic |
|
|
System.Reflection.BindingFlags.Instance);
|
|
|
|
if (method == null)
|
|
{
|
|
Assert.Fail("Método EvaluateBranching no encontrado");
|
|
return null;
|
|
}
|
|
|
|
return (string)method.Invoke(_dialogueSystem, new object[] { node });
|
|
}
|
|
|
|
/// <summary>
|
|
/// Registra un nodo en el diálogo actual para que GetNextNode lo encuentre.
|
|
/// </summary>
|
|
private void RegisterNode(string nodeId)
|
|
{
|
|
var field = typeof(DialogueSystem).GetField("_currentDialogue",
|
|
System.Reflection.BindingFlags.NonPublic |
|
|
System.Reflection.BindingFlags.Instance);
|
|
var dialogue = (DialogueData)field.GetValue(_dialogueSystem);
|
|
dialogue.nodes.Add(new DialogueNode { id = nodeId });
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|