More unit tests, fixes
This commit is contained in:
@@ -0,0 +1,193 @@
|
||||
using PkmnLib.Dynamic.Events;
|
||||
using PkmnLib.Dynamic.Models;
|
||||
using PkmnLib.Dynamic.Models.Choices;
|
||||
using PkmnLib.Dynamic.ScriptHandling;
|
||||
using PkmnLib.Plugin.Gen7.Scripts.Moves;
|
||||
using PkmnLib.Plugin.Gen7.Scripts.Pokemon;
|
||||
using PkmnLib.Static.Utils;
|
||||
|
||||
namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for the <see cref="BeakBlast"/> move script and its attached <see cref="BeakBlastEffect"/>.
|
||||
/// Behavior is verified against the Bulbapedia page for Beak Blast (Generation VII).
|
||||
/// </summary>
|
||||
public class BeakBlastTests
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a fully mocked setup for driving <see cref="BeakBlast.OnBeforeTurnStart"/>.
|
||||
/// </summary>
|
||||
private static (BeakBlast script, ITurnChoice choice, IPokemon user, IScriptSet volatileSet, EventHook eventHook)
|
||||
CreateChargeSetup(bool hasBattleData = true)
|
||||
{
|
||||
var script = new BeakBlast();
|
||||
var user = Substitute.For<IPokemon>();
|
||||
user.GetScripts().Returns(_ => new ScriptIterator(Array.Empty<IEnumerable<ScriptContainer>>()));
|
||||
IScriptSet volatileSet = new ScriptSet(user);
|
||||
user.Volatile.Returns(volatileSet);
|
||||
|
||||
var eventHook = new EventHook();
|
||||
if (hasBattleData)
|
||||
{
|
||||
var battle = Substitute.For<IBattle>();
|
||||
battle.EventHook.Returns(eventHook);
|
||||
var battleData = Substitute.For<IPokemonBattleData>();
|
||||
battleData.Battle.Returns(battle);
|
||||
user.BattleData.Returns(battleData);
|
||||
}
|
||||
else
|
||||
{
|
||||
user.BattleData.Returns((IPokemonBattleData?)null);
|
||||
}
|
||||
|
||||
var choice = Substitute.For<ITurnChoice>();
|
||||
choice.User.Returns(user);
|
||||
|
||||
return (script, choice, user, volatileSet, eventHook);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bulbapedia: "The user charges up at the beginning of the turn and executes Beak Blast at -3 priority."
|
||||
/// Before the turn starts, the charging <see cref="BeakBlastEffect"/> is attached to the user.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task OnBeforeTurnStart_UserInBattle_AddsChargeEffect()
|
||||
{
|
||||
// Arrange
|
||||
var (script, choice, _, volatileSet, _) = CreateChargeSetup();
|
||||
|
||||
// Act
|
||||
script.OnBeforeTurnStart(choice);
|
||||
|
||||
// Assert
|
||||
await Assert.That(volatileSet.Get<BeakBlastEffect>()).IsNotNull();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bulbapedia: "Its charging message is displayed before any other moves."
|
||||
/// The charge fires a dialog event when the turn starts.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task OnBeforeTurnStart_UserInBattle_FiresChargingMessage()
|
||||
{
|
||||
// Arrange
|
||||
var (script, choice, user, _, eventHook) = CreateChargeSetup();
|
||||
DialogEvent? capturedEvent = null;
|
||||
eventHook.Handler += (_, args) =>
|
||||
{
|
||||
if (args is DialogEvent dialogEvent)
|
||||
capturedEvent = dialogEvent;
|
||||
};
|
||||
|
||||
// Act
|
||||
script.OnBeforeTurnStart(choice);
|
||||
|
||||
// Assert
|
||||
await Assert.That(capturedEvent).IsNotNull();
|
||||
await Assert.That(capturedEvent!.Message).IsEqualTo("beak_blast_charge");
|
||||
await Assert.That(capturedEvent.Parameters!["user"]).IsEqualTo(user);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Technical test: a Pokémon without <see cref="IPokemon.BattleData"/> is not in battle, so no charging
|
||||
/// phase starts.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task OnBeforeTurnStart_NoBattleData_DoesNotAddChargeEffect()
|
||||
{
|
||||
// Arrange
|
||||
var (script, choice, _, volatileSet, _) = CreateChargeSetup(false);
|
||||
|
||||
// Act
|
||||
script.OnBeforeTurnStart(choice);
|
||||
|
||||
// Assert
|
||||
await Assert.That(volatileSet.Get<BeakBlastEffect>()).IsNull();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bulbapedia: "All who make contact with the user during the charging phase are burned."
|
||||
/// The charging phase ends when the move executes, so the <see cref="BeakBlastEffect"/> is removed again.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task OnSecondaryEffect_MoveExecuted_RemovesChargeEffect()
|
||||
{
|
||||
// Arrange
|
||||
var script = new BeakBlast();
|
||||
var move = Substitute.For<IExecutingMove>();
|
||||
var target = Substitute.For<IPokemon>();
|
||||
var user = Substitute.For<IPokemon>();
|
||||
user.GetScripts().Returns(_ => new ScriptIterator(Array.Empty<IEnumerable<ScriptContainer>>()));
|
||||
IScriptSet volatileSet = new ScriptSet(user);
|
||||
user.Volatile.Returns(volatileSet);
|
||||
move.User.Returns(user);
|
||||
volatileSet.Add(new BeakBlastEffect());
|
||||
|
||||
// Act
|
||||
script.OnSecondaryEffect(move, target, 0);
|
||||
|
||||
// Assert
|
||||
await Assert.That(volatileSet.Get<BeakBlastEffect>()).IsNull();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a fully mocked setup for driving <see cref="BeakBlastEffect.OnIncomingHit"/>.
|
||||
/// </summary>
|
||||
private static (BeakBlastEffect effect, IExecutingMove move, IPokemon target, IPokemon attacker)
|
||||
CreateIncomingHitSetup(bool isContact)
|
||||
{
|
||||
var effect = new BeakBlastEffect();
|
||||
var move = Substitute.For<IExecutingMove>();
|
||||
var target = Substitute.For<IPokemon>();
|
||||
var hitData = Substitute.For<IHitData>();
|
||||
hitData.IsContact.Returns(isContact);
|
||||
move.GetHitData(target, 0).Returns(hitData);
|
||||
|
||||
var attacker = Substitute.For<IPokemon>();
|
||||
move.User.Returns(attacker);
|
||||
|
||||
return (effect, move, target, attacker);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper to extract the status name from a Pokémon's received <see cref="IPokemon.SetStatus"/> calls.
|
||||
/// </summary>
|
||||
private static string? GetStatusSet(IPokemon pokemon)
|
||||
{
|
||||
var call = pokemon.ReceivedCalls().FirstOrDefault(c => c.GetMethodInfo().Name == "SetStatus");
|
||||
return call != null ? ((StringKey)call.GetArguments()[0]!).ToString() : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bulbapedia: "All who make contact with the user during the charging phase are burned."
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task OnIncomingHit_ContactMove_BurnsAttacker()
|
||||
{
|
||||
// Arrange
|
||||
var (effect, move, target, attacker) = CreateIncomingHitSetup(true);
|
||||
|
||||
// Act
|
||||
effect.OnIncomingHit(move, target, 0);
|
||||
|
||||
// Assert
|
||||
await Assert.That(GetStatusSet(attacker)).IsEqualTo("burned");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bulbapedia: "All who make contact with the user during the charging phase are burned."
|
||||
/// A hit that does not make contact must not burn the attacker.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task OnIncomingHit_NonContactMove_DoesNotBurnAttacker()
|
||||
{
|
||||
// Arrange
|
||||
var (effect, move, target, attacker) = CreateIncomingHitSetup(false);
|
||||
|
||||
// Act
|
||||
effect.OnIncomingHit(move, target, 0);
|
||||
|
||||
// Assert
|
||||
await Assert.That(attacker.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "SetStatus")).IsFalse();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user