using PkmnLib.Dynamic.Models; using PkmnLib.Plugin.Gen7.Scripts.Moves; using PkmnLib.Static; namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves; /// /// Tests for the move script. /// Gen VII Bulbapedia behavior: "Guard Swap switches the user's Defense stat stages and Special Defense /// stat stages with the target's Defense stat stages and Special Defense stat stages." /// public class GuardSwapTests { private static (GuardSwap script, IExecutingMove move, IPokemon user, IPokemon target) CreateTestSetup( sbyte userDefense, sbyte userSpecialDefense, sbyte targetDefense, sbyte targetSpecialDefense) { var script = new GuardSwap(); var move = Substitute.For(); var user = Substitute.For(); user.StatBoost.Returns(new StatBoostStatisticSet(0, 0, userDefense, 0, userSpecialDefense, 0)); move.User.Returns(user); var target = Substitute.For(); target.StatBoost.Returns(new StatBoostStatisticSet(0, 0, targetDefense, 0, targetSpecialDefense, 0)); return (script, move, user, target); } /// /// Helper to extract the arguments of the received ChangeStatBoost call for the given statistic, or /// null when that stat was not changed. Received-call inspection is used instead of NSubstitute /// argument matchers because the trailing EventBatchId parameter cannot be bound by Arg.Any. /// 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); /// /// Bulbapedia: "Guard Swap switches the user's Defense stat stages ... with the target's Defense stat /// stages." The user at +2 Defense and the target at -1 Defense must end up at -1 and +2 /// respectively, so the user is changed by -3 and the target by +3. /// [Test] public async Task OnSecondaryEffect_DifferentDefenseStages_StagesAreSwapped() { // Arrange var (script, move, user, target) = CreateTestSetup(2, 0, -1, 0); // Act script.OnSecondaryEffect(move, target, 0); // Assert var userArgs = GetStatBoostArgs(user, Statistic.Defense); var targetArgs = GetStatBoostArgs(target, Statistic.Defense); await Assert.That(userArgs).IsNotNull(); await Assert.That((sbyte)userArgs![1]!).IsEqualTo((sbyte)-3); await Assert.That(targetArgs).IsNotNull(); await Assert.That((sbyte)targetArgs![1]!).IsEqualTo((sbyte)3); } /// /// Bulbapedia: the swap also covers the "Special Defense stat stages". The user at -2 Special Defense /// and the target at +3 must end up at +3 and -2 respectively, so the user is changed by +5 and the /// target by -5. /// [Test] public async Task OnSecondaryEffect_DifferentSpecialDefenseStages_StagesAreSwapped() { // Arrange var (script, move, user, target) = CreateTestSetup(0, -2, 0, 3); // Act script.OnSecondaryEffect(move, target, 0); // Assert var userArgs = GetStatBoostArgs(user, Statistic.SpecialDefense); var targetArgs = GetStatBoostArgs(target, Statistic.SpecialDefense); await Assert.That(userArgs).IsNotNull(); await Assert.That((sbyte)userArgs![1]!).IsEqualTo((sbyte)5); await Assert.That(targetArgs).IsNotNull(); await Assert.That((sbyte)targetArgs![1]!).IsEqualTo((sbyte)-5); } /// /// Bulbapedia: the swap is unconditional — the change is applied with force so clamping or /// prevention effects cannot block the exchange of stages. /// [Test] public async Task OnSecondaryEffect_Always_ChangesAreForced() { // Arrange var (script, move, user, target) = CreateTestSetup(2, 0, -1, 0); // Act script.OnSecondaryEffect(move, target, 0); // Assert var userArgs = GetStatBoostArgs(user, Statistic.Defense); var targetArgs = GetStatBoostArgs(target, Statistic.Defense); await Assert.That((bool)userArgs![3]!).IsTrue(); // forced await Assert.That((bool)targetArgs![3]!).IsTrue(); // forced await Assert.That((bool)userArgs[2]!).IsTrue(); // self-inflicted for the user await Assert.That((bool)targetArgs[2]!).IsFalse(); // not self-inflicted for the target } /// /// Bulbapedia: only the "Defense stat stages and Special Defense stat stages" are switched — the /// other stat stages are left untouched. /// [Test] public async Task OnSecondaryEffect_Always_OtherStagesUnchanged() { // Arrange var (script, move, user, target) = CreateTestSetup(2, -2, -1, 3); // Act script.OnSecondaryEffect(move, target, 0); // Assert await Assert.That(GetStatBoostArgs(user, Statistic.Attack)).IsNull(); await Assert.That(GetStatBoostArgs(user, Statistic.SpecialAttack)).IsNull(); await Assert.That(GetStatBoostArgs(user, Statistic.Speed)).IsNull(); await Assert.That(GetStatBoostArgs(target, Statistic.Attack)).IsNull(); } /// /// When both Pokémon have equal stages, swapping is a net-zero change for both sides. /// [Test] public async Task OnSecondaryEffect_EqualStages_ChangeIsZero() { // Arrange var (script, move, user, target) = CreateTestSetup(1, 2, 1, 2); // Act script.OnSecondaryEffect(move, target, 0); // Assert var userDefenseArgs = GetStatBoostArgs(user, Statistic.Defense); var userSpecialDefenseArgs = GetStatBoostArgs(user, Statistic.SpecialDefense); await Assert.That((sbyte)userDefenseArgs![1]!).IsEqualTo((sbyte)0); await Assert.That((sbyte)userSpecialDefenseArgs![1]!).IsEqualTo((sbyte)0); } }