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 script, which implements Mimic. /// Behavior is verified against the Bulbapedia page for Mimic (Generation VII). /// The script is expected to copy the target's last used move through the engine's /// hook, the same pattern used by . /// public class MimicTests { private const string NotImplementedReason = "Mimic is not implemented: Mimic.cs contains only a FIXME comment and implements no script hooks, so " + "the move has no effect (Bulbapedia: 'Mimic copies a move from the target')"; /// /// Creates a mocked whose move data has the given name. /// private static ILearnedMove CreateLearnedMove(string name) { var learned = Substitute.For(); var data = Substitute.For(); data.Name.Returns(new StringKey(name)); learned.MoveData.Returns(data); return learned; } /// /// Creates a fully mocked executing Mimic move. The user knows one other move in slot 0 and Mimic in /// slot 1. If is null, the target has not used a move yet. /// private static (IExecutingMove move, IPokemon user, IPokemon target, IHitData hitData) CreateTestSetup( string? targetLastUsedMove, string userOtherMove = "swords_dance") { var move = Substitute.For(); var user = Substitute.For(); var target = Substitute.For(); move.User.Returns(user); var hitData = Substitute.For(); move.GetHitData(target, 0).Returns(hitData); // The user knows another move in slot 0, and Mimic in slot 1; Mimic is the chosen move. var mimicMove = CreateLearnedMove("mimic"); move.ChosenMove.Returns(mimicMove); user.Moves.Returns(new[] { CreateLearnedMove(userOtherMove), mimicMove }); var battleData = Substitute.For(); if (targetLastUsedMove != null) { var lastChoice = Substitute.For(); var lastUsedMove = CreateLearnedMove(targetLastUsedMove); lastChoice.ChosenMove.Returns(lastUsedMove); battleData.LastMoveChoice.Returns(lastChoice); } else { battleData.LastMoveChoice.Returns((IMoveChoice?)null); } target.BattleData.Returns(battleData); return (move, user, target, hitData); } /// /// Asserts that the script implements the secondary effect hook, and returns it as /// that hook. Every behavior of Mimic requires this hook to exist at all. /// private static async Task GetSecondaryEffectHook() { var mimic = new Mimic(); await Assert.That(mimic is IScriptOnSecondaryEffect).IsTrue(); return (IScriptOnSecondaryEffect)(object)mimic; } /// /// Helper that checks whether was called on the hit data. /// private static bool ReceivedFail(IHitData hitData) => hitData.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "Fail"); /// /// Helper to extract the arguments of the user's received call. /// private static (StringKey moveName, byte index)? GetLearnedMove(IPokemon user) { var call = user.ReceivedCalls().FirstOrDefault(c => c.GetMethodInfo().Name == "LearnMove"); if (call == null) return null; return ((StringKey)call.GetArguments()[0]!, (byte)call.GetArguments()[2]!); } /// /// Bulbapedia: "Mimic copies a move from the target. The user will retain the copied move in Mimic's /// place" and (Generation II onwards) "Mimic now copies the target's last used move." /// The target's last used move must be learned in the move slot that holds Mimic. /// [Test, TestFailing(NotImplementedReason)] public async Task OnSecondaryEffect_TargetUsedMove_UserLearnsTargetsLastMoveInMimicsSlot() { // Arrange var mimic = await GetSecondaryEffectHook(); var (move, user, target, _) = CreateTestSetup("tackle"); // Act mimic.OnSecondaryEffect(move, target, 0); // Assert - the copied move replaces Mimic, which sits in slot 1. var learned = GetLearnedMove(user); await Assert.That(learned.HasValue).IsTrue(); await Assert.That(learned!.Value.moveName).IsEqualTo(new StringKey("tackle")); await Assert.That(learned.Value.index).IsEqualTo((byte)1); } /// /// Bulbapedia: (Generation II onwards) "Mimic now copies the target's last used move." /// If the target has not used a move yet, there is nothing to copy and the hit must fail. /// [Test, TestFailing(NotImplementedReason)] public async Task OnSecondaryEffect_TargetHasNotUsedAMove_FailsHit() { // Arrange var mimic = await GetSecondaryEffectHook(); var (move, user, target, hitData) = CreateTestSetup(null); // Act mimic.OnSecondaryEffect(move, target, 0); // Assert await Assert.That(ReceivedFail(hitData)).IsTrue(); await Assert.That(GetLearnedMove(user).HasValue).IsFalse(); } /// /// Bulbapedia: (Generation II onwards) "It now fails to copy the moves Sketch, Transform, Struggle, /// Metronome, or any move the user already knows.", (Generation IV onwards) "Mimic will fail to copy /// Chatter.", and (Generation V onwards) "Mimic is no longer able to copy Transform." /// [Test, TestFailing(NotImplementedReason), Arguments("sketch"), Arguments("transform"), Arguments("struggle"), Arguments("metronome"), Arguments("chatter")] public async Task OnSecondaryEffect_TargetsLastMoveIsUncopyable_FailsHit(string uncopyableMove) { // Arrange var mimic = await GetSecondaryEffectHook(); var (move, user, target, hitData) = CreateTestSetup(uncopyableMove); // Act mimic.OnSecondaryEffect(move, target, 0); // Assert await Assert.That(ReceivedFail(hitData)).IsTrue(); await Assert.That(GetLearnedMove(user).HasValue).IsFalse(); } /// /// Bulbapedia: (Generation II onwards) "It now fails to copy ... any move the user already knows." /// [Test, TestFailing(NotImplementedReason)] public async Task OnSecondaryEffect_UserAlreadyKnowsTargetsLastMove_FailsHit() { // Arrange var mimic = await GetSecondaryEffectHook(); // The target's last used move is Tackle, which the user already knows in slot 0. var (move, user, target, hitData) = CreateTestSetup("tackle", "tackle"); // Act mimic.OnSecondaryEffect(move, target, 0); // Assert await Assert.That(ReceivedFail(hitData)).IsTrue(); await Assert.That(GetLearnedMove(user).HasValue).IsFalse(); } }