This commit is contained in:
@@ -0,0 +1,242 @@
|
||||
using PkmnLib.Dynamic.Models;
|
||||
using PkmnLib.Dynamic.ScriptHandling;
|
||||
using PkmnLib.Dynamic.ScriptHandling.Registry;
|
||||
using PkmnLib.Plugin.Gen7.Scripts.Moves;
|
||||
using PkmnLib.Plugin.Gen7.Scripts.Side;
|
||||
using PkmnLib.Static.Utils;
|
||||
|
||||
namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for the <see cref="LunarDance"/> move script and its side effect script
|
||||
/// <see cref="LunarDanceEffect"/>.
|
||||
/// Gen VII Bulbapedia behavior: "Lunar Dance causes the user to faint, but restores the HP and PP of the
|
||||
/// Pokémon that is sent out to take its place as well as removing all status conditions (except
|
||||
/// fainting).", where from Generation V to VII "The receiving Pokémon is now sent out at the end of the
|
||||
/// turn after all other Pokémon have moved."
|
||||
/// </summary>
|
||||
public class LunarDanceTests
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a fully mocked test setup for the <see cref="LunarDance"/> move script, with the user at the
|
||||
/// given position and the side's volatile scripts backed by a real <see cref="ScriptSet"/>.
|
||||
/// </summary>
|
||||
private static (LunarDance script, IExecutingMove move, IPokemon user, IScriptSet sideVolatile) CreateMoveSetup(
|
||||
byte position = 1)
|
||||
{
|
||||
var script = new LunarDance();
|
||||
var move = Substitute.For<IExecutingMove>();
|
||||
|
||||
var side = Substitute.For<IBattleSide>();
|
||||
// ScriptSet.Add runs the IScriptPreventVolatileAdd hook over the owner's scripts; give the mock a
|
||||
// real (empty) iterator so the hook pass runs.
|
||||
side.GetScripts().Returns(_ => new ScriptIterator(Array.Empty<IEnumerable<ScriptContainer>>()));
|
||||
IScriptSet sideVolatile = new ScriptSet(side);
|
||||
side.VolatileScripts.Returns(sideVolatile);
|
||||
|
||||
var battleData = Substitute.For<IPokemonBattleData>();
|
||||
battleData.BattleSide.Returns(side);
|
||||
battleData.Position.Returns(position);
|
||||
|
||||
var user = Substitute.For<IPokemon>();
|
||||
user.BattleData.Returns(battleData);
|
||||
move.User.Returns(user);
|
||||
|
||||
return (script, move, user, sideVolatile);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a mocked replacement Pokémon with the given maximum HP for
|
||||
/// <see cref="LunarDanceEffect"/> tests.
|
||||
/// </summary>
|
||||
private static IPokemon CreateReplacement(uint maxHp = 200)
|
||||
{
|
||||
var pokemon = Substitute.For<IPokemon>();
|
||||
pokemon.MaxHealth.Returns(maxHp);
|
||||
return pokemon;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper to extract the heal amount from a substitute's received Heal calls, or null when Heal was
|
||||
/// never called.
|
||||
/// </summary>
|
||||
private static uint? GetHealAmount(IPokemon pokemon)
|
||||
{
|
||||
var call = pokemon.ReceivedCalls().FirstOrDefault(c => c.GetMethodInfo().Name == "Heal");
|
||||
return call != null ? (uint)call.GetArguments()[0]! : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bulbapedia (Generations V to VII): "The receiving Pokémon is now sent out at the end of the turn" —
|
||||
/// using the move leaves a pending <see cref="LunarDanceEffect"/> on the user's side instead of healing
|
||||
/// anything directly.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task OnSecondaryEffect_UserInBattle_AddsLunarDanceEffectToUserSide()
|
||||
{
|
||||
// Arrange
|
||||
var (script, move, _, sideVolatile) = CreateMoveSetup();
|
||||
|
||||
// Act
|
||||
script.OnSecondaryEffect(move, Substitute.For<IPokemon>(), 0);
|
||||
|
||||
// Assert
|
||||
await Assert.That(sideVolatile.Contains(ScriptUtils.ResolveName<LunarDanceEffect>())).IsTrue();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bulbapedia: "Lunar Dance causes the user to faint".
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task OnSecondaryEffect_UserInBattle_UserFaints()
|
||||
{
|
||||
// Arrange
|
||||
var (script, move, user, _) = CreateMoveSetup();
|
||||
|
||||
// Act
|
||||
script.OnSecondaryEffect(move, Substitute.For<IPokemon>(), 0);
|
||||
|
||||
// Assert
|
||||
await Assert.That(user.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "Faint")).IsTrue();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bulbapedia: the dance benefits the Pokémon "sent out to take its place" — the pending effect is
|
||||
/// bound to the position the user occupied, so a replacement entering that position is healed.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task OnSecondaryEffect_UserAtPosition_EffectTargetsUsersPosition()
|
||||
{
|
||||
// Arrange
|
||||
var (script, move, _, sideVolatile) = CreateMoveSetup(1);
|
||||
script.OnSecondaryEffect(move, Substitute.For<IPokemon>(), 0);
|
||||
var effect = (LunarDanceEffect)sideVolatile.Get(new StringKey("lunar_dance"))!.Script!;
|
||||
var replacement = CreateReplacement();
|
||||
|
||||
// Act
|
||||
effect.OnSwitchIn(replacement, 1);
|
||||
|
||||
// Assert
|
||||
await Assert.That(GetHealAmount(replacement)).IsNotNull();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Technical test: outside of battle (no <see cref="IPokemon.BattleData"/>) the hook does nothing and
|
||||
/// does not throw.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task OnSecondaryEffect_NoBattleData_DoesNothing()
|
||||
{
|
||||
// Arrange
|
||||
var script = new LunarDance();
|
||||
var move = Substitute.For<IExecutingMove>();
|
||||
var user = Substitute.For<IPokemon>();
|
||||
user.BattleData.Returns((IPokemonBattleData?)null);
|
||||
move.User.Returns(user);
|
||||
|
||||
// Act
|
||||
script.OnSecondaryEffect(move, Substitute.For<IPokemon>(), 0);
|
||||
|
||||
// Assert
|
||||
await Assert.That(user.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "Faint")).IsFalse();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bulbapedia: "restores the HP ... of the Pokémon that is sent out to take its place" — the
|
||||
/// replacement switching into the dancer's position is healed by its full maximum HP.
|
||||
/// </summary>
|
||||
[Test, Arguments(200u), Arguments(1u), Arguments(999u)]
|
||||
public async Task OnSwitchIn_MatchingPosition_HealsReplacementToFullHp(uint maxHp)
|
||||
{
|
||||
// Arrange
|
||||
var effect = new LunarDanceEffect(1);
|
||||
var replacement = CreateReplacement(maxHp);
|
||||
|
||||
// Act
|
||||
effect.OnSwitchIn(replacement, 1);
|
||||
|
||||
// Assert
|
||||
await Assert.That(GetHealAmount(replacement)!.Value).IsEqualTo(maxHp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bulbapedia: "restores the HP and PP of the Pokémon that is sent out" — unlike Healing Wish, Lunar
|
||||
/// Dance also restores the replacement's PP.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task OnSwitchIn_MatchingPosition_RestoresPP()
|
||||
{
|
||||
// Arrange
|
||||
var effect = new LunarDanceEffect(1);
|
||||
var replacement = CreateReplacement();
|
||||
|
||||
// Act
|
||||
effect.OnSwitchIn(replacement, 1);
|
||||
|
||||
// Assert
|
||||
await Assert.That(replacement.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "RestoreAllPP")).IsTrue();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bulbapedia: "removing all status conditions (except fainting)".
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task OnSwitchIn_MatchingPosition_CuresStatus()
|
||||
{
|
||||
// Arrange
|
||||
var effect = new LunarDanceEffect(1);
|
||||
var replacement = CreateReplacement();
|
||||
|
||||
// Act
|
||||
effect.OnSwitchIn(replacement, 1);
|
||||
|
||||
// Assert
|
||||
await Assert.That(replacement.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "ClearStatus")).IsTrue();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bulbapedia (Generations V to VII): the dance is consumed by the Pokémon sent out at the position —
|
||||
/// unlike Generation VIII onwards there is no check on the replacement's HP, status or PP, so the
|
||||
/// effect is removed from the side after the first switch-in.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task OnSwitchIn_MatchingPosition_EffectIsRemovedFromSide()
|
||||
{
|
||||
// Arrange
|
||||
var side = Substitute.For<IBattleSide>();
|
||||
side.GetScripts().Returns(_ => new ScriptIterator(Array.Empty<IEnumerable<ScriptContainer>>()));
|
||||
IScriptSet sideVolatile = new ScriptSet(side);
|
||||
var effect = new LunarDanceEffect(1);
|
||||
sideVolatile.Add(effect);
|
||||
|
||||
// Act
|
||||
effect.OnSwitchIn(CreateReplacement(), 1);
|
||||
|
||||
// Assert
|
||||
await Assert.That(sideVolatile.Contains(ScriptUtils.ResolveName<LunarDanceEffect>())).IsFalse();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bulbapedia: the dance applies to the Pokémon sent out in the user's place — a Pokémon switching in
|
||||
/// at a different position on the side is not restored and does not consume the effect.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task OnSwitchIn_DifferentPosition_DoesNotRestoreAndEffectPersists()
|
||||
{
|
||||
// Arrange
|
||||
var side = Substitute.For<IBattleSide>();
|
||||
side.GetScripts().Returns(_ => new ScriptIterator(Array.Empty<IEnumerable<ScriptContainer>>()));
|
||||
IScriptSet sideVolatile = new ScriptSet(side);
|
||||
var effect = new LunarDanceEffect(1);
|
||||
sideVolatile.Add(effect);
|
||||
var otherPokemon = CreateReplacement();
|
||||
|
||||
// Act
|
||||
effect.OnSwitchIn(otherPokemon, 0);
|
||||
|
||||
// Assert
|
||||
await Assert.That(GetHealAmount(otherPokemon)).IsNull();
|
||||
await Assert.That(otherPokemon.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "ClearStatus")).IsFalse();
|
||||
await Assert.That(sideVolatile.Contains(ScriptUtils.ResolveName<LunarDanceEffect>())).IsTrue();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user