using PkmnLib.Dynamic.Events; 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: "Memento causes the user to faint and in return lowers the Attack and /// Special Attack stat of the target by two stages each." From Generation V onward the user does not faint /// if the move fails (no target, miss, blocked by a substitute) - that part is engine behavior, since /// only runs when the move successfully hits. /// public class MementoTests { private static (Memento script, IExecutingMove move, IBattlePokemon target, IBattlePokemon user) CreateTestSetup() { var script = new Memento(); var move = Substitute.For(); var target = Substitute.For(); var user = Substitute.For(); move.User.Returns(user); return (script, move, target, user); } /// /// Helper to extract the arguments of the received ChangeStatBoost call for the given stat, or null /// when no boost for that stat was requested. Received-call inspection is used instead of NSubstitute /// argument matchers because the trailing parameter cannot be bound by /// Arg.Any. /// private static object?[]? GetStatBoostArgs(IBattlePokemon pokemon, Statistic stat) => pokemon.ReceivedCalls().Where(c => c.GetMethodInfo().Name == "ChangeStatBoost").Select(c => c.GetArguments()) .FirstOrDefault(args => (Statistic)args[0]! == stat); /// /// Bulbapedia: "Memento causes the user to faint and in return lowers the Attack and Special Attack stat /// of the target by two stages each." /// The Attack drop is caused by the opponent's move, so it is not self-inflicted and not forced. /// [Test] public async Task OnSecondaryEffect_Hit_LowersTargetAttackByTwoStages() { // Arrange var (script, move, target, _) = CreateTestSetup(); // Act script.OnSecondaryEffect(move, target, 0); // Assert var args = GetStatBoostArgs(target, Statistic.Attack); await Assert.That(args).IsNotNull(); await Assert.That((sbyte)args![1]!).IsEqualTo((sbyte)-2); await Assert.That((bool)args[2]!).IsFalse(); await Assert.That((bool)args[3]!).IsFalse(); } /// /// Bulbapedia: "Memento causes the user to faint and in return lowers the Attack and Special Attack stat /// of the target by two stages each." /// The Special Attack drop is caused by the opponent's move, so it is not self-inflicted and not forced. /// [Test] public async Task OnSecondaryEffect_Hit_LowersTargetSpecialAttackByTwoStages() { // Arrange var (script, move, target, _) = CreateTestSetup(); // Act script.OnSecondaryEffect(move, target, 0); // Assert var args = GetStatBoostArgs(target, Statistic.SpecialAttack); await Assert.That(args).IsNotNull(); await Assert.That((sbyte)args![1]!).IsEqualTo((sbyte)-2); await Assert.That((bool)args[2]!).IsFalse(); await Assert.That((bool)args[3]!).IsFalse(); } /// /// Bulbapedia: "Memento causes the user to faint and in return lowers the Attack and Special Attack stat /// of the target by two stages each." /// Only those two stats are lowered; no other stat changes are requested. /// [Test] public async Task OnSecondaryEffect_Hit_OnlyLowersAttackAndSpecialAttack() { // Arrange var (script, move, target, _) = CreateTestSetup(); // Act script.OnSecondaryEffect(move, target, 0); // Assert var statBoostCalls = target.ReceivedCalls().Count(c => c.GetMethodInfo().Name == "ChangeStatBoost"); await Assert.That(statBoostCalls).IsEqualTo(2); } /// /// Bulbapedia: "Memento causes the user to faint". /// The faint is not move damage, so it uses . /// [Test] public async Task OnSecondaryEffect_Hit_UserFaints() { // Arrange var (script, move, target, user) = CreateTestSetup(); // Act script.OnSecondaryEffect(move, target, 0); // Assert var faintCall = user.ReceivedCalls().FirstOrDefault(c => c.GetMethodInfo().Name == "Faint"); await Assert.That(faintCall).IsNotNull(); await Assert.That((DamageSource)faintCall!.GetArguments()[0]!).IsEqualTo(DamageSource.Misc); } /// /// Bulbapedia (Generation V onward): "the user faints if it successfully hits but cannot lower stats due /// to abilities like Clear Body or stats already at -6." /// A substitute's returns false by default (the stat drops were /// prevented), but the user must still faint. /// [Test] public async Task OnSecondaryEffect_StatDropsPrevented_UserStillFaints() { // Arrange var (script, move, target, user) = CreateTestSetup(); // Act - the unconfigured ChangeStatBoost calls return false, as if blocked by Clear Body script.OnSecondaryEffect(move, target, 0); // Assert await Assert.That(user.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "Faint")).IsTrue(); } /// /// Technical test: both stat drops are batched under the same so they are /// shown as a single event to the client. /// [Test] public async Task OnSecondaryEffect_Hit_StatDropsShareEventBatch() { // Arrange var (script, move, target, _) = CreateTestSetup(); // Act script.OnSecondaryEffect(move, target, 0); // Assert var attackBatch = (EventBatchId)GetStatBoostArgs(target, Statistic.Attack)![4]!; var specialAttackBatch = (EventBatchId)GetStatBoostArgs(target, Statistic.SpecialAttack)![4]!; await Assert.That(attackBatch).IsEqualTo(specialAttackBatch); } }