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.Plugin.Gen7.Scripts.Utils;
using PkmnLib.Static;
using PkmnLib.Static.Utils;
namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves;
///
/// Tests for the move script.
/// Behavior is verified against the Bulbapedia page for Baton Pass (Generation VII).
/// In this codebase, volatile scripts that are transferred by Baton Pass are marked with
/// (e.g. ).
///
public class BatonPassTests
{
///
/// Creates a mocked Pokémon with a real volatile and a real
/// .
///
private static IPokemon CreateMockPokemon(out IScriptSet volatileSet)
{
var pokemon = Substitute.For();
pokemon.GetScripts().Returns(_ => new ScriptIterator(Array.Empty>()));
var set = new ScriptSet(pokemon);
pokemon.Volatile.Returns(set);
pokemon.StatBoost.Returns(new StatBoostStatisticSet());
volatileSet = set;
return pokemon;
}
///
/// Creates a fully mocked test setup for Baton Pass tests. The Pokémon to switch in is stored in the
/// move choice's under the to_switch key.
///
private static (BatonPass script, IExecutingMove move, IPokemon user, IPokemon toSwitch, IBattleSide side,
IScriptSet userVolatile, IScriptSet switchInVolatile) CreateTestSetup()
{
var script = new BatonPass();
var move = Substitute.For();
var user = CreateMockPokemon(out var userVolatile);
var toSwitch = CreateMockPokemon(out var switchInVolatile);
var moveChoice = Substitute.For();
moveChoice.AdditionalData.Returns(new Dictionary { { "to_switch", toSwitch } });
move.MoveChoice.Returns(moveChoice);
var side = Substitute.For();
var battle = Substitute.For();
battle.Sides.Returns(new[] { side });
var battleData = Substitute.For();
battleData.Battle.Returns(battle);
battleData.SideIndex.Returns((byte)0);
battleData.Position.Returns((byte)1);
user.BattleData.Returns(battleData);
move.User.Returns(user);
return (script, move, user, toSwitch, side, userVolatile, switchInVolatile);
}
///
/// Bulbapedia: "Baton Pass switches out the user and passes several effects to the Pokémon switched in
/// its place."
/// The user is swapped out for the chosen Pokémon on its own field position.
///
[Test]
public void OnSecondaryEffect_SwitchTargetChosen_SwitchesOutUser()
{
// Arrange
var (script, move, user, toSwitch, side, _, _) = CreateTestSetup();
// Act
script.OnSecondaryEffect(move, user, 0);
// Assert
side.Received(1).SwapPokemon((byte)1, toSwitch);
}
///
/// Bulbapedia: "Baton Pass transfers all temporary stat stage increases and decreases".
/// Each stat stage (including evasion and accuracy) is copied to the Pokémon that is switched in.
///
[Test, Arguments(Statistic.Attack, (sbyte)6), Arguments(Statistic.Defense, (sbyte)-6),
Arguments(Statistic.SpecialAttack, (sbyte)2), Arguments(Statistic.SpecialDefense, (sbyte)-1),
Arguments(Statistic.Speed, (sbyte)3), Arguments(Statistic.Evasion, (sbyte)1),
Arguments(Statistic.Accuracy, (sbyte)-2)]
public async Task OnSecondaryEffect_StatBoost_TransferredToSwitchIn(Statistic stat, sbyte boost)
{
// Arrange
var (script, move, user, toSwitch, _, _, _) = CreateTestSetup();
user.StatBoost.SetStatistic(stat, boost);
// Act
script.OnSecondaryEffect(move, user, 0);
// Assert
await Assert.That(toSwitch.StatBoost.GetStatistic(stat)).IsEqualTo(boost);
}
///
/// Bulbapedia: "Baton Pass transfers all temporary stat stage increases and decreases".
/// After the stat stages are copied, the switched-in Pokémon's boosted stats are recalculated.
///
[Test]
public void OnSecondaryEffect_StatBoostsTransferred_RecalculatesSwitchInStats()
{
// Arrange
var (script, move, user, toSwitch, _, _, _) = CreateTestSetup();
// Act
script.OnSecondaryEffect(move, user, 0);
// Assert
toSwitch.Received(1).RecalculateBoostedStats();
}
///
/// Bulbapedia: "Baton Pass switches out the user and passes several effects to the Pokémon switched in
/// its place."
/// A volatile script that is marked as transferable (, such as
/// ) is passed to the switched-in Pokémon.
///
[Test]
public async Task OnSecondaryEffect_TransferableVolatile_PassedToSwitchIn()
{
// Arrange
var (script, move, user, _, _, userVolatile, switchInVolatile) = CreateTestSetup();
userVolatile.Add(new AutotomizeEffect());
// Act
script.OnSecondaryEffect(move, user, 0);
// Assert
await Assert.That(switchInVolatile.Get()).IsNotNull();
}
///
/// Bulbapedia: "Baton Pass also passes some volatile status conditions, namely confusion, getting pumped,
/// escape prevention, seeding, Curse, Substitute, Ingrain, Aqua Ring" — effects outside that list are not
/// passed. The consecutive-protection counter () is not transferable,
/// so it must not end up on the switched-in Pokémon.
///
[Test]
public async Task OnSecondaryEffect_NonTransferableVolatile_NotPassedToSwitchIn()
{
// Arrange
var (script, move, user, _, _, userVolatile, switchInVolatile) = CreateTestSetup();
userVolatile.Add(new ProtectionFailureScript());
// Act
script.OnSecondaryEffect(move, user, 0);
// Assert
await Assert.That(switchInVolatile.Get()).IsNull();
}
///
/// Bulbapedia: "Baton Pass switches out the user and passes several effects to the Pokémon switched in
/// its place."
/// A transferable volatile must be passed even when the user also has a non-transferable volatile that
/// was added before it.
///
[Test]
public async Task OnSecondaryEffect_TransferableVolatileAfterNonTransferable_StillPassedToSwitchIn()
{
// Arrange
var (script, move, user, _, _, userVolatile, switchInVolatile) = CreateTestSetup();
userVolatile.Add(new ProtectionFailureScript());
userVolatile.Add(new AutotomizeEffect());
// Act
script.OnSecondaryEffect(move, user, 0);
// Assert
await Assert.That(switchInVolatile.Get()).IsNotNull();
await Assert.That(switchInVolatile.Get()).IsNull();
}
///
/// Bulbapedia: "Baton Pass switches out the user".
/// The user's volatile scripts leave the field with it: after the switch its volatile containers are empty.
///
[Test]
public async Task OnSecondaryEffect_UserSwitchedOut_UserVolatilesCleared()
{
// Arrange
var (script, move, user, _, _, userVolatile, _) = CreateTestSetup();
userVolatile.Add(new AutotomizeEffect());
// Act
script.OnSecondaryEffect(move, user, 0);
// Assert
await Assert.That(userVolatile.Get()).IsNull();
}
///
/// Technical test: without any there is no Pokémon to switch to,
/// so no switch happens.
///
[Test]
public void OnSecondaryEffect_NoAdditionalData_DoesNotSwitch()
{
// Arrange
var (script, move, user, _, side, _, _) = CreateTestSetup();
move.MoveChoice.AdditionalData.Returns((Dictionary?)null);
// Act
script.OnSecondaryEffect(move, user, 0);
// Assert
side.DidNotReceive().SwapPokemon(Arg.Any(), Arg.Any());
}
///
/// Technical test: if the stored switch target is not a Pokémon, no switch happens.
///
[Test]
public void OnSecondaryEffect_SwitchTargetNotAPokemon_DoesNotSwitch()
{
// Arrange
var (script, move, user, _, side, _, _) = CreateTestSetup();
move.MoveChoice.AdditionalData.Returns(new Dictionary { { "to_switch", null } });
// Act
script.OnSecondaryEffect(move, user, 0);
// Assert
side.DidNotReceive().SwapPokemon(Arg.Any(), Arg.Any());
}
///
/// Technical test: if the user has no , the script returns without
/// switching and without clearing the user's volatile scripts.
///
[Test]
public async Task OnSecondaryEffect_NoBattleData_DoesNotSwitch()
{
// Arrange
var (script, move, user, _, side, userVolatile, _) = CreateTestSetup();
user.BattleData.Returns((IPokemonBattleData?)null);
userVolatile.Add(new AutotomizeEffect());
// Act
script.OnSecondaryEffect(move, user, 0);
// Assert
side.DidNotReceive().SwapPokemon(Arg.Any(), Arg.Any());
await Assert.That(userVolatile.Get()).IsNotNull();
}
}