using PkmnLib.Dynamic.Models; using PkmnLib.Dynamic.Models.Choices; using PkmnLib.Dynamic.ScriptHandling; using PkmnLib.Plugin.Gen7.Scripts.Moves; using PkmnLib.Static.Moves; using PkmnLib.Static.Utils; namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves; /// /// Tests for the move script. /// Gen VII Bulbapedia behavior: "Instruct causes the target to immediately repeat its most recent move". It /// fails against targets with no prior moves, and "cannot force repetition of Instruct itself, Sketch, /// Transform, Mimic, King's Shield, Struggle, moves requiring recharge (like Hyper Beam), moves with charging /// turns (like Dig), moves calling other moves (like Metronome), Z-Moves, or Max Moves." /// public class InstructTests { /// /// Creates a fully mocked test setup for Instruct tests. When is given, /// the target has a repeatable last move choice of that name; otherwise it has not moved yet. /// private static (Instruct script, IExecutingMove move, IBattle battle, IPokemon target, IHitData hitData, IMoveChoice ? lastChoice) CreateTestSetup(string? lastMoveName) { var script = new Instruct(); var move = Substitute.For(); var user = Substitute.For(); move.User.Returns(user); var battle = Substitute.For(); var userBattleData = Substitute.For(); userBattleData.Battle.Returns(battle); user.BattleData.Returns(userBattleData); var target = Substitute.For(); target.IsUsable.Returns(true); var targetBattleData = Substitute.For(); target.BattleData.Returns(targetBattleData); IMoveChoice? lastChoice = null; if (lastMoveName != null) { lastChoice = Substitute.For(); // The choice needs a real (empty) script iterator so the turn runner's hook pass works on it. lastChoice.GetScripts().Returns(_ => new ScriptIterator(new List>())); lastChoice.User.Returns(Substitute.For()); var moveData = Substitute.For(); moveData.Name.Returns(new StringKey(lastMoveName)); var learnedMove = Substitute.For(); learnedMove.MoveData.Returns(moveData); lastChoice.ChosenMove.Returns(learnedMove); } targetBattleData.LastMoveChoice.Returns(lastChoice); var hitData = Substitute.For(); move.GetHitData(target, 0).Returns(hitData); return (script, move, battle, target, hitData, lastChoice); } /// /// Bulbapedia: Instruct "fails against targets with no prior moves." A target that has not used a move /// yet has no choice to repeat, so the hit fails. /// [Test] public void OnSecondaryEffect_TargetHasNotMovedYet_HitFails() { // Arrange var (script, move, _, target, hitData, _) = CreateTestSetup(null); // Act script.OnSecondaryEffect(move, target, 0); // Assert hitData.Received(1).Fail(); } /// /// Bulbapedia: the repeated move must actually be usable (e.g. it fails if the move has no PP left). /// When the battle reports the last choice as unusable, the hit fails. /// [Test] public void OnSecondaryEffect_LastChoiceCannotBeUsed_HitFails() { // Arrange var (script, move, battle, target, hitData, lastChoice) = CreateTestSetup("tackle"); battle.CanUse(lastChoice!).Returns(false); // Act script.OnSecondaryEffect(move, target, 0); // Assert hitData.Received(1).Fail(); } /// /// Bulbapedia: "Instruct causes the target to immediately repeat its most recent move". With a usable /// last move choice the move does not fail (the choice is handed to the turn runner for execution). /// [Test] public void OnSecondaryEffect_LastChoiceUsable_HitDoesNotFail() { // Arrange var (script, move, battle, target, hitData, lastChoice) = CreateTestSetup("tackle"); battle.CanUse(lastChoice!).Returns(true); // Act script.OnSecondaryEffect(move, target, 0); // Assert hitData.DidNotReceive().Fail(); } /// /// Technical test: an unusable (e.g. fainted) target is skipped entirely — nothing is executed and the /// hit is not failed. /// [Test] public void OnSecondaryEffect_TargetNotUsable_DoesNothing() { // Arrange var (script, move, _, target, hitData, _) = CreateTestSetup("tackle"); target.IsUsable.Returns(false); // Act script.OnSecondaryEffect(move, target, 0); // Assert hitData.DidNotReceive().Fail(); } /// /// Technical test: if the user has no , the script does nothing. /// [Test] public void OnSecondaryEffect_UserHasNoBattleData_DoesNothing() { // Arrange var (script, move, _, target, hitData, _) = CreateTestSetup("tackle"); move.User.BattleData.Returns((IPokemonBattleData?)null); // Act script.OnSecondaryEffect(move, target, 0); // Assert hitData.DidNotReceive().Fail(); } /// /// Bulbapedia: "The move cannot force repetition of Instruct itself, Sketch, Transform, Mimic, King's /// Shield, Struggle, moves requiring recharge (like Hyper Beam), moves with charging turns (like Dig), /// moves calling other moves (like Metronome), Z-Moves, or Max Moves." When the target's last move is /// such a move, Instruct fails. /// [Test] public void OnSecondaryEffect_LastMoveIsInstructItself_HitFails() { // Arrange var (script, move, battle, target, hitData, lastChoice) = CreateTestSetup("instruct"); battle.CanUse(lastChoice!).Returns(true); // Act script.OnSecondaryEffect(move, target, 0); // Assert hitData.Received(1).Fail(); } }