using PkmnLib.Dynamic.Models; using PkmnLib.Dynamic.ScriptHandling; using PkmnLib.Plugin.Gen7.Scripts.Moves; using PkmnLib.Plugin.Gen7.Scripts.Side; using PkmnLib.Static.Utils; using TUnit.Assertions.AssertConditions.Throws; namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves; /// /// Tests for the move script. /// Gen VII Bulbapedia behavior: "Brick Break removes Light Screen and Reflect", from Generation IV onward /// "from the target's side of the field, even if the target is an ally", and from Generation V onwards /// "Brick Break can also remove Aurora Veil." /// public class BrickBreakTests { /// /// Creates a mocked Pokémon whose is the given side. /// private static IPokemon CreatePokemonOnSide(IBattleSide side) { var pokemon = Substitute.For(); var battleData = Substitute.For(); battleData.BattleSide.Returns(side); pokemon.BattleData.Returns(battleData); return pokemon; } /// /// Creates a fully mocked test setup where the user targets a Pokémon on the opposing side. /// private static (BrickBreak script, IExecutingMove move, IPokemon user, IScriptSet userSideScripts, IScriptSet targetSideScripts) CreateTestSetup() { var script = new BrickBreak(); var move = Substitute.For(); var userSideScripts = Substitute.For(); var userSide = Substitute.For(); userSide.VolatileScripts.Returns(userSideScripts); var targetSideScripts = Substitute.For(); var targetSide = Substitute.For(); targetSide.VolatileScripts.Returns(targetSideScripts); var user = CreatePokemonOnSide(userSide); move.User.Returns(user); var target = CreatePokemonOnSide(targetSide); move.Targets.Returns(new IPokemon?[] { target }); return (script, move, user, userSideScripts, targetSideScripts); } /// /// Bulbapedia (Generation IV onward): "Brick Break now removes Light Screen and Reflect from the target's /// side of the field, even if the target is an ally." The screen effects are removed from the target's /// side before the move hits. The script names match and /// . /// [Test, Arguments("reflect"), Arguments("light_screen")] public async Task OnBeforeMove_ScreenOnTargetSide_RemovesScreenFromTargetSide(string screenScriptName) { // Arrange var (script, move, _, _, targetSideScripts) = CreateTestSetup(); // Act script.OnBeforeMove(move); // Assert targetSideScripts.Received(1).Remove(new StringKey(screenScriptName)); await Assert.That(targetSideScripts.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "Remove")).IsTrue(); } /// /// Bulbapedia (Generation V onwards): "Brick Break can also remove Aurora Veil." /// The is removed from the target's side as well. /// [Test] public async Task OnBeforeMove_AuroraVeilOnTargetSide_RemovesAuroraVeilFromTargetSide() { // Arrange var (script, move, _, _, targetSideScripts) = CreateTestSetup(); // Act script.OnBeforeMove(move); // Assert targetSideScripts.Received(1).Remove(new StringKey("aurora_veil")); await Assert.That(targetSideScripts.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "Remove")).IsTrue(); } /// /// Bulbapedia (Generation IV onward): "Brick Break now removes Light Screen and Reflect from the target's /// side of the field". When the target is an opponent, screens on the user's own side stay up. /// [Test] public async Task OnBeforeMove_ScreensOnUserSide_DoesNotRemoveScreensFromUserSide() { // Arrange var (script, move, _, userSideScripts, _) = CreateTestSetup(); // Act script.OnBeforeMove(move); // Assert await Assert.That(userSideScripts.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "Remove")).IsFalse(); } /// /// Bulbapedia (Generation IV onward): "Brick Break now removes Light Screen and Reflect from the target's /// side of the field, even if the target is an ally." An ally shares the user's side, so targeting an ally /// removes the screens from the user's own side. /// [Test, TestFailing("BrickBreak.OnBeforeMove excludes the user's own side unconditionally, so targeting an " + "ally removes nothing (Bulbapedia: 'removes Light Screen and Reflect from the target's " + "side of the field, even if the target is an ally')")] public async Task OnBeforeMove_AllyTargetOnUserSide_RemovesScreensFromUserSide() { // Arrange var (script, move, user, userSideScripts, _) = CreateTestSetup(); var ally = CreatePokemonOnSide(user.BattleData!.BattleSide); move.Targets.Returns(new IPokemon?[] { ally }); // Act script.OnBeforeMove(move); // Assert await Assert.That(userSideScripts.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "Remove")).IsTrue(); } /// /// Bulbapedia: "Brick Break removes Light Screen and Reflect [...], then inflicts damage." /// When no screens are up there is nothing to remove; the move simply proceeds without error and the /// sides remain unaffected. /// [Test] public async Task OnBeforeMove_NoScreensPresent_LeavesSidesUnchanged() { // Arrange var script = new BrickBreak(); var move = Substitute.For(); // Real, empty script sets so the removals (if any wrongly happen) would be observable as mutations. var userSideScripts = new ScriptSet(Substitute.For()); var targetSideScripts = new ScriptSet(Substitute.For()); var userSide = Substitute.For(); userSide.VolatileScripts.Returns(userSideScripts); var targetSide = Substitute.For(); targetSide.VolatileScripts.Returns(targetSideScripts); var user = CreatePokemonOnSide(userSide); move.User.Returns(user); var target = CreatePokemonOnSide(targetSide); move.Targets.Returns(new IPokemon?[] { target }); // Act script.OnBeforeMove(move); // Assert await Assert.That(userSideScripts.Count).IsEqualTo(0); await Assert.That(targetSideScripts.Count).IsEqualTo(0); } /// /// Technical test: without any targets (e.g. outside of battle) the script does nothing and does not throw. /// [Test] public async Task OnBeforeMove_NoTargets_DoesNotThrow() { // Arrange var script = new BrickBreak(); var move = Substitute.For(); var user = Substitute.For(); user.BattleData.Returns((IPokemonBattleData?)null); move.User.Returns(user); move.Targets.Returns(Array.Empty()); // Act & Assert await Assert.That(() => script.OnBeforeMove(move)).ThrowsNothing(); } }