Files
Ajedrez_Purgatorio/Assets/Tests/EditMode/Dialogue/DialogueSystemBranchingTests.cs
T
jimmyabv f7d56066c0 feat: Purgatory Final duel vs La Muerte + SFX wiring + runtime options menu
- 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
2026-08-24 19:39:52 -03:00

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
}
}