229 lines
8.5 KiB
C#
229 lines
8.5 KiB
C#
using PkmnLib.Dynamic.Models;
|
|
using PkmnLib.Plugin.Gen7.Scripts.Moves;
|
|
using PkmnLib.Static;
|
|
using PkmnLib.Static.Species;
|
|
using PkmnLib.Static.Utils;
|
|
|
|
namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves;
|
|
|
|
/// <summary>
|
|
/// Tests for the <see cref="GearUp"/> move script.
|
|
/// Gen VII Bulbapedia behavior: "Gear Up raises the Attack and Special Attack stats of allied Pokémon
|
|
/// (including the user) with the Ability Plus or Minus by one stage each."
|
|
/// </summary>
|
|
public class GearUpTests
|
|
{
|
|
/// <summary>
|
|
/// Creates a fully mocked test setup for Gear Up tests. The user is on side 0; the given side lists
|
|
/// are installed as the battle's sides.
|
|
/// </summary>
|
|
private static (GearUp script, IExecutingMove move, IPokemon user) CreateTestSetup(
|
|
params IReadOnlyList<IPokemon?>[] sidePokemon)
|
|
{
|
|
var script = new GearUp();
|
|
var move = Substitute.For<IExecutingMove>();
|
|
|
|
var battle = Substitute.For<IBattle>();
|
|
var sides = sidePokemon.Select(pokemon =>
|
|
{
|
|
var side = Substitute.For<IBattleSide>();
|
|
side.Pokemon.Returns(pokemon);
|
|
return side;
|
|
}).ToArray();
|
|
battle.Sides.Returns(sides);
|
|
|
|
var user = Substitute.For<IPokemon>();
|
|
var battleData = Substitute.For<IPokemonBattleData>();
|
|
battleData.Battle.Returns(battle);
|
|
battleData.SideIndex.Returns((byte)0);
|
|
user.BattleData.Returns(battleData);
|
|
move.User.Returns(user);
|
|
|
|
return (script, move, user);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a mocked Pokémon whose <see cref="IPokemon.ActiveAbility"/> has the given name, or no
|
|
/// ability at all when <paramref name="abilityName"/> is null.
|
|
/// </summary>
|
|
private static IPokemon CreatePokemon(string? abilityName)
|
|
{
|
|
var pokemon = Substitute.For<IPokemon>();
|
|
if (abilityName != null)
|
|
{
|
|
var ability = Substitute.For<IAbility>();
|
|
ability.Name.Returns(new StringKey(abilityName));
|
|
pokemon.ActiveAbility.Returns(ability);
|
|
}
|
|
else
|
|
{
|
|
pokemon.ActiveAbility.Returns((IAbility?)null);
|
|
}
|
|
|
|
return pokemon;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Helper to extract the arguments of the received ChangeStatBoost call for the given statistic, or
|
|
/// null when that stat was not boosted. Received-call inspection is used instead of NSubstitute
|
|
/// argument matchers because the trailing EventBatchId parameter cannot be bound by <c>Arg.Any</c>.
|
|
/// </summary>
|
|
private static object?[]? GetStatBoostArgs(IPokemon pokemon, Statistic stat) =>
|
|
pokemon.ReceivedCalls().Where(c => c.GetMethodInfo().Name == "ChangeStatBoost").Select(c => c.GetArguments())
|
|
.FirstOrDefault(args => (Statistic)args[0]! == stat);
|
|
|
|
/// <summary>
|
|
/// Bulbapedia: "Gear Up raises the Attack and Special Attack stats of allied Pokémon ... with the
|
|
/// Ability Plus ... by one stage each." An ally's boost is not self-inflicted.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task OnSecondaryEffect_AllyWithPlus_AttackAndSpecialAttackRaisedByOneStage()
|
|
{
|
|
// Arrange
|
|
var ally = CreatePokemon("plus");
|
|
var (script, move, _) = CreateTestSetup(new List<IPokemon?> { ally });
|
|
|
|
// Act
|
|
script.OnSecondaryEffect(move, Substitute.For<IPokemon>(), 0);
|
|
|
|
// Assert
|
|
var attackArgs = GetStatBoostArgs(ally, Statistic.Attack);
|
|
var specialAttackArgs = GetStatBoostArgs(ally, Statistic.SpecialAttack);
|
|
await Assert.That(attackArgs).IsNotNull();
|
|
await Assert.That((sbyte)attackArgs![1]!).IsEqualTo((sbyte)1);
|
|
await Assert.That((bool)attackArgs[2]!).IsFalse(); // not self-inflicted
|
|
await Assert.That(specialAttackArgs).IsNotNull();
|
|
await Assert.That((sbyte)specialAttackArgs![1]!).IsEqualTo((sbyte)1);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bulbapedia: the boost also applies to allies "with the Ability ... Minus".
|
|
/// </summary>
|
|
[Test]
|
|
public async Task OnSecondaryEffect_AllyWithMinus_AttackAndSpecialAttackRaisedByOneStage()
|
|
{
|
|
// Arrange
|
|
var ally = CreatePokemon("minus");
|
|
var (script, move, _) = CreateTestSetup(new List<IPokemon?> { ally });
|
|
|
|
// Act
|
|
script.OnSecondaryEffect(move, Substitute.For<IPokemon>(), 0);
|
|
|
|
// Assert
|
|
await Assert.That(GetStatBoostArgs(ally, Statistic.Attack)).IsNotNull();
|
|
await Assert.That(GetStatBoostArgs(ally, Statistic.SpecialAttack)).IsNotNull();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bulbapedia: the boost applies to allied Pokémon "including the user" — a user with Plus boosts
|
|
/// itself, and that boost is self-inflicted.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task OnSecondaryEffect_UserHasPlus_UserBoostIsSelfInflicted()
|
|
{
|
|
// Arrange
|
|
var (script, move, user) = CreateTestSetup();
|
|
var ability = Substitute.For<IAbility>();
|
|
ability.Name.Returns(new StringKey("plus"));
|
|
user.ActiveAbility.Returns(ability);
|
|
var side = Substitute.For<IBattleSide>();
|
|
side.Pokemon.Returns(new List<IPokemon?> { user });
|
|
user.BattleData!.Battle.Sides.Returns(new[] { side });
|
|
|
|
// Act
|
|
script.OnSecondaryEffect(move, Substitute.For<IPokemon>(), 0);
|
|
|
|
// Assert
|
|
var attackArgs = GetStatBoostArgs(user, Statistic.Attack);
|
|
await Assert.That(attackArgs).IsNotNull();
|
|
await Assert.That((bool)attackArgs![2]!).IsTrue(); // self-inflicted for the user itself
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bulbapedia: only allies "with the Ability Plus or Minus" are raised — an ally with a different
|
|
/// Ability is unaffected.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task OnSecondaryEffect_AllyWithOtherAbility_NotBoosted()
|
|
{
|
|
// Arrange
|
|
var ally = CreatePokemon("levitate");
|
|
var (script, move, _) = CreateTestSetup(new List<IPokemon?> { ally });
|
|
|
|
// Act
|
|
script.OnSecondaryEffect(move, Substitute.For<IPokemon>(), 0);
|
|
|
|
// Assert
|
|
await Assert.That(ally.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "ChangeStatBoost")).IsFalse();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bulbapedia: the boost applies to "allied Pokémon" — an opposing Pokémon with Plus is not raised.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task OnSecondaryEffect_OpponentWithPlus_NotBoosted()
|
|
{
|
|
// Arrange
|
|
var opponent = CreatePokemon("plus");
|
|
var (script, move, _) = CreateTestSetup(new List<IPokemon?>(), new List<IPokemon?> { opponent });
|
|
|
|
// Act
|
|
script.OnSecondaryEffect(move, Substitute.For<IPokemon>(), 0);
|
|
|
|
// Assert
|
|
await Assert.That(opponent.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "ChangeStatBoost")).IsFalse();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Technical test: an ally without an active Ability is skipped without throwing.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task OnSecondaryEffect_AllyWithoutAbility_NotBoosted()
|
|
{
|
|
// Arrange
|
|
var ally = CreatePokemon(null);
|
|
var (script, move, _) = CreateTestSetup(new List<IPokemon?> { ally });
|
|
|
|
// Act
|
|
script.OnSecondaryEffect(move, Substitute.For<IPokemon>(), 0);
|
|
|
|
// Assert
|
|
await Assert.That(ally.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "ChangeStatBoost")).IsFalse();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Technical test: empty (null) slots on the user's side are skipped without throwing.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task OnSecondaryEffect_EmptyPokemonSlot_IsSkipped()
|
|
{
|
|
// Arrange
|
|
var ally = CreatePokemon("plus");
|
|
var (script, move, _) = CreateTestSetup(new List<IPokemon?> { null, ally });
|
|
|
|
// Act
|
|
script.OnSecondaryEffect(move, Substitute.For<IPokemon>(), 0);
|
|
|
|
// Assert
|
|
await Assert.That(GetStatBoostArgs(ally, Statistic.Attack)).IsNotNull();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Technical test: when the user has no <see cref="IPokemon.BattleData"/> (it is not in a battle),
|
|
/// the script does nothing instead of throwing.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task OnSecondaryEffect_NullBattleData_DoesNothing()
|
|
{
|
|
// Arrange
|
|
var ally = CreatePokemon("plus");
|
|
var (script, move, user) = CreateTestSetup(new List<IPokemon?> { ally });
|
|
user.BattleData.Returns((IPokemonBattleData?)null);
|
|
|
|
// Act
|
|
script.OnSecondaryEffect(move, Substitute.For<IPokemon>(), 0);
|
|
|
|
// Assert
|
|
await Assert.That(ally.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "ChangeStatBoost")).IsFalse();
|
|
}
|
|
} |