using PkmnLib.Dynamic.Models; using PkmnLib.Dynamic.ScriptHandling; using PkmnLib.Plugin.Gen7.Scripts.Moves; using PkmnLib.Plugin.Gen7.Scripts.Pokemon; namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves; /// /// Tests for the script, which implements Focus Energy. /// Behavior is verified against the Bulbapedia page for Focus Energy (Generation VII behavior: the /// Generation II base effect with the Generation III change applied). /// The lasting part of the effect (the raised critical hit ratio) is implemented by the /// volatile script, which the move script is responsible for attaching /// to the user. /// public class FocusEnergyTests { /// /// Creates a fully mocked test setup for Focus Energy tests. /// private static (FocusEnergy focusEnergy, IExecutingMove move, IBattlePokemon target, IScriptSet volatileSet, IHitData hitData) CreateTestSetup() { var focusEnergy = new FocusEnergy(); var move = Substitute.For(); var target = Substitute.For(); var hitData = Substitute.For(); move.GetHitData(target, 0).Returns(hitData); var volatileSet = Substitute.For(); target.Volatile.Returns(volatileSet); return (focusEnergy, move, target, volatileSet, hitData); } /// /// Helper to extract the script passed to the volatile set's Add call. /// private static Script? GetAddedScript(IScriptSet volatileSet) { var call = volatileSet.ReceivedCalls().FirstOrDefault(c => c.GetMethodInfo().Name == "Add"); return call?.GetArguments()[0] as Script; } /// /// Bulbapedia (Generation II): "Focus Energy now permanently increases the user's critical hit ratio." /// The lasting effect is implemented by attaching a volatile script to the /// target of the move (Focus Energy is self-targeted, so the target is the user). /// [Test] public async Task OnSecondaryEffect_Called_AddsFocusEnergyEffectToTargetVolatile() { // Arrange var (focusEnergy, move, target, volatileSet, _) = CreateTestSetup(); // Act focusEnergy.OnSecondaryEffect(move, target, 0); // Assert await Assert.That(GetAddedScript(volatileSet) is FocusEnergyEffect).IsTrue(); } /// /// Bulbapedia (Generations III to V, unchanged through Generation VII): "Focus Energy now increases the /// user's critical hit ratio by two stages instead of one." /// The volatile effect applied by must therefore raise the critical hit stage /// by two when the affected Pokémon attacks. /// [Test, Arguments((byte)0, (byte)2), Arguments((byte)1, (byte)3), Arguments((byte)2, (byte)4)] public async Task OnSecondaryEffect_AppliedEffect_RaisesCriticalHitStageByTwoStages(byte initialStage, byte expectedStage) { // Arrange var (focusEnergy, move, target, volatileSet, _) = CreateTestSetup(); focusEnergy.OnSecondaryEffect(move, target, 0); var addedScript = GetAddedScript(volatileSet); await Assert.That(addedScript is IScriptChangeCriticalStage).IsTrue(); // Act - run the applied effect's critical stage hook, as would happen when the user attacks var stage = initialStage; ((IScriptChangeCriticalStage)addedScript!).ChangeCriticalStage(move, target, 0, ref stage); // Assert await Assert.That(stage).IsEqualTo(expectedStage); } /// /// Bulbapedia: "The effect of Focus Energy cannot stack, and it will fail if the user is already under its /// effect." (Stated for Generation I and never reverted by a later generational change, so still the /// behavior in Generation VII.) /// When the user already has the volatile script, the hit must fail. /// [Test] public async Task OnSecondaryEffect_UserAlreadyUnderEffect_HitFails() { // Arrange var (focusEnergy, move, target, volatileSet, hitData) = CreateTestSetup(); volatileSet.Contains().Returns(true); // Act focusEnergy.OnSecondaryEffect(move, target, 0); // Assert await Assert.That(hitData.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "Fail")).IsTrue(); } /// /// Bulbapedia: "The effect of Focus Energy cannot stack". A second use on a user that is already under the /// effect must not attach a second volatile script, which would otherwise /// stack another two critical hit stages on top of the existing ones. /// [Test] public async Task OnSecondaryEffect_UserAlreadyUnderEffect_DoesNotAddEffectAgain() { // Arrange var (focusEnergy, move, target, volatileSet, _) = CreateTestSetup(); volatileSet.Contains().Returns(true); // Act focusEnergy.OnSecondaryEffect(move, target, 0); // Assert await Assert.That(GetAddedScript(volatileSet)).IsNull(); } }