using PkmnLib.Dynamic.Events; using PkmnLib.Dynamic.Models; using PkmnLib.Dynamic.ScriptHandling; using PkmnLib.Plugin.Gen7.Scripts.Moves; using PkmnLib.Plugin.Gen7.Scripts.Pokemon; using PkmnLib.Static; using PkmnLib.Static.Utils; namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves; /// /// Tests for the move script. /// Gen VII Bulbapedia behavior: Flatter raises the target's Special Attack stat by one stage and confuses it. /// public class FlatterTests { private static (Flatter script, IExecutingMove move, IBattlePokemon target, IScriptSet targetVolatile) CreateTestSetup() { var script = new Flatter(); var move = Substitute.For(); var user = Substitute.For(); move.User.Returns(user); var target = Substitute.For(); var targetVolatile = Substitute.For(); target.Volatile.Returns(targetVolatile); return (script, move, target, targetVolatile); } /// /// Helper to extract the arguments of the received ChangeStatBoost call, or null when no stat boost /// was requested. Received-call inspection is used instead of NSubstitute argument matchers because /// the trailing parameter cannot be bound by Arg.Any (its /// parameterless constructor initializes a fresh id, so it never equals the matcher's default value). /// private static object?[]? GetStatBoostArgs(IBattlePokemon pokemon) { var call = pokemon.ReceivedCalls().FirstOrDefault(c => c.GetMethodInfo().Name == "ChangeStatBoost"); return call?.GetArguments(); } /// /// Bulbapedia: Flatter "raises the target's Special Attack stat by one stage". /// The boost is caused by the opponent's move, so it is not self-inflicted. /// [Test] public async Task OnSecondaryEffect_Always_RaisesTargetSpecialAttackByOneStage() { // Arrange var (script, move, target, _) = CreateTestSetup(); // Act script.OnSecondaryEffect(move, target, 0); // Assert var args = GetStatBoostArgs(target); await Assert.That(args).IsNotNull(); await Assert.That((Statistic)args![0]!).IsEqualTo(Statistic.SpecialAttack); await Assert.That((sbyte)args[1]!).IsEqualTo((sbyte)1); await Assert.That((bool)args[2]!).IsFalse(); // not self-inflicted await Assert.That((bool)args[3]!).IsFalse(); // not forced } /// /// Bulbapedia: Flatter raises the target's Special Attack "and confuses it". The confusion is applied /// through the target's script set. /// [Test] public async Task OnSecondaryEffect_Always_ConfusesTarget() { // Arrange var (script, move, target, targetVolatile) = CreateTestSetup(); // Act script.OnSecondaryEffect(move, target, 0); // Assert targetVolatile.Received(1).StackOrAdd(new StringKey("confusion"), Arg.Any>()); await Assert.That(targetVolatile.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "StackOrAdd")).IsTrue(); } /// /// Bulbapedia: Flatter "confuses" the target. The instantiation function passed to the volatile script /// set creates a script. /// [Test] public async Task OnSecondaryEffect_ConfusionInstantiation_CreatesConfusionScript() { // Arrange var (script, move, target, targetVolatile) = CreateTestSetup(); // Act script.OnSecondaryEffect(move, target, 0); // Assert var call = targetVolatile.ReceivedCalls().First(c => c.GetMethodInfo().Name == "StackOrAdd"); var instantiation = (Func)call.GetArguments()[1]!; await Assert.That(instantiation() is Confusion).IsTrue(); } /// /// Bulbapedia: "Even if the target's Special Attack is already at +6 stages, it will still become /// confused." A substitute's returns false by default (the /// boost failed), yet the confusion is still applied. /// [Test] public async Task OnSecondaryEffect_StatBoostFails_TargetStillConfused() { // Arrange var (script, move, target, targetVolatile) = CreateTestSetup(); // Act script.OnSecondaryEffect(move, target, 0); // Assert - the unconfigured ChangeStatBoost returned false, but the target was still confused await Assert.That(GetStatBoostArgs(target)).IsNotNull(); await Assert.That(targetVolatile.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "StackOrAdd")).IsTrue(); } /// /// Bulbapedia: "Even if the target is already confused, or cannot become confused due to Safeguard or /// Own Tempo, its Special Attack will still be raised." When the volatile set refuses to add the /// confusion script (returns null), the Special Attack boost still happens. /// [Test] public async Task OnSecondaryEffect_ConfusionNotAdded_SpecialAttackStillRaised() { // Arrange var (script, move, target, targetVolatile) = CreateTestSetup(); targetVolatile.StackOrAdd(Arg.Any(), Arg.Any>()).Returns((ScriptContainer?)null); // Act script.OnSecondaryEffect(move, target, 0); // Assert var args = GetStatBoostArgs(target); await Assert.That(args).IsNotNull(); await Assert.That((Statistic)args![0]!).IsEqualTo(Statistic.SpecialAttack); await Assert.That((sbyte)args[1]!).IsEqualTo((sbyte)1); } }