using PkmnLib.Dynamic.Models; using PkmnLib.Dynamic.ScriptHandling; using PkmnLib.Dynamic.ScriptHandling.Registry; using PkmnLib.Plugin.Gen7.Scripts.Moves; using PkmnLib.Plugin.Gen7.Scripts.Pokemon; using PkmnLib.Static.Species; using PkmnLib.Static.Utils; namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves; /// /// Tests for the move script. /// Gen VII Bulbapedia behavior: "Heal Bell cures the user, all Pokémon in the user's party, and the user's /// allies, of all status conditions (sleep, poison, paralysis, freeze, and burn)." Since Generation VI: /// "Heal Bell no longer affects active Pokémon with the Ability Soundproof. Inactive Pokémon are healed /// regardless of Abilities." /// public class HealBellTests { /// /// Creates a mocked party member with a mocked set and /// , optionally active on the battlefield and with a named ability. /// private static IPokemon CreatePartyMember(bool onBattlefield = false, string? abilityName = null) { var pokemon = Substitute.For(); var volatileSet = Substitute.For(); pokemon.Volatile.Returns(volatileSet); var battleData = Substitute.For(); battleData.IsOnBattlefield.Returns(onBattlefield); pokemon.BattleData.Returns(battleData); if (abilityName != null) { var ability = Substitute.For(); ability.Name.Returns(new StringKey(abilityName)); pokemon.ActiveAbility.Returns(ability); } return pokemon; } /// /// Creates a fully mocked test setup: the user plus the given other party members form a single party /// in the battle. Returns the script, the executing move and the user (which is the move's target in /// most tests, as Heal Bell targets the user's side). /// private static (HealBell script, IExecutingMove move, IPokemon user) CreateTestSetup( params IPokemon?[] otherPartyMembers) { var script = new HealBell(); var user = CreatePartyMember(true); var members = new List { user }; members.AddRange(otherPartyMembers); var party = Substitute.For(); party.GetEnumerator().Returns(_ => members.GetEnumerator()); var battleParty = Substitute.For(); battleParty.Party.Returns(party); var battle = Substitute.For(); battle.Parties.Returns(new[] { battleParty }); user.BattleData!.Battle.Returns(battle); var move = Substitute.For(); move.User.Returns(user); return (script, move, user); } /// /// Helper that checks whether a mocked Pokémon received a call. /// private static bool ReceivedClearStatus(IPokemon pokemon) => pokemon.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "ClearStatus"); /// /// Bulbapedia: "Heal Bell cures the user, all Pokémon in the user's party, and the user's allies, of all /// status conditions (sleep, poison, paralysis, freeze, and burn)." Every non-empty slot of the user's /// party is cured, including inactive Pokémon; empty party slots are skipped without throwing. /// [Test] public async Task OnSecondaryEffect_TargetInUsersParty_CuresStatusOfAllPartyMembers() { // Arrange var inactiveMember = CreatePartyMember(); var activeMember = CreatePartyMember(true); var (script, move, user) = CreateTestSetup(inactiveMember, activeMember, null); // Act script.OnSecondaryEffect(move, user, 0); // Assert await Assert.That(ReceivedClearStatus(user)).IsTrue(); await Assert.That(ReceivedClearStatus(inactiveMember)).IsTrue(); await Assert.That(ReceivedClearStatus(activeMember)).IsTrue(); } /// /// Bulbapedia: Heal Bell cures "all Pokémon in the user's party". A target that is not in any party /// (e.g. outside of the battle's known parties) results in no effect and no exception. /// [Test] public async Task OnSecondaryEffect_TargetNotInAnyParty_DoesNothing() { // Arrange var (script, move, user) = CreateTestSetup(); var strayTarget = CreatePartyMember(); // Act script.OnSecondaryEffect(move, strayTarget, 0); // Assert - nobody is cured because no party contains the target await Assert.That(ReceivedClearStatus(user)).IsFalse(); await Assert.That(ReceivedClearStatus(strayTarget)).IsFalse(); } /// /// Technical test: outside of battle the user has no ; the script /// does nothing and does not throw. /// [Test] public async Task OnSecondaryEffect_UserHasNoBattleData_DoesNothing() { // Arrange var (script, move, user) = CreateTestSetup(); user.BattleData.Returns((IPokemonBattleData?)null); // Act script.OnSecondaryEffect(move, user, 0); // Assert await Assert.That(ReceivedClearStatus(user)).IsFalse(); } /// /// Bulbapedia (Generation VI, still applicable in Gen VII): "Inactive Pokémon are healed regardless of /// Abilities." A party member with Soundproof that is not on the battlefield is still cured. /// [Test] public async Task OnSecondaryEffect_InactiveSoundproofPartyMember_StillCured() { // Arrange var soundproofMember = CreatePartyMember(false, "soundproof"); var (script, move, user) = CreateTestSetup(soundproofMember); // Act script.OnSecondaryEffect(move, user, 0); // Assert await Assert.That(ReceivedClearStatus(soundproofMember)).IsTrue(); } /// /// Bulbapedia (Generation VI, still applicable in Gen VII): "Heal Bell no longer affects active Pokémon /// with the Ability Soundproof." An active party member with Soundproof is not cured. /// [Test] public async Task OnSecondaryEffect_ActiveSoundproofPartyMember_NotCured() { // Arrange var soundproofMember = CreatePartyMember(true, "soundproof"); var (script, move, user) = CreateTestSetup(soundproofMember); // Act script.OnSecondaryEffect(move, user, 0); // Assert await Assert.That(ReceivedClearStatus(soundproofMember)).IsFalse(); } /// /// Bulbapedia: Heal Bell cures "all status conditions (sleep, poison, paralysis, freeze, and burn)". /// Confusion is a volatile status, not a status condition, and is not cured by Heal Bell; a confused /// party member keeps its volatile script. /// [Test] public async Task OnSecondaryEffect_ConfusedPartyMember_ConfusionNotCured() { // Arrange var confusedMember = CreatePartyMember(); var confusionKey = ScriptUtils.ResolveName(); confusedMember.Volatile.Contains(confusionKey).Returns(true); var (script, move, user) = CreateTestSetup(confusedMember); // Act script.OnSecondaryEffect(move, user, 0); // Assert - the confusion volatile is not removed await Assert.That(confusedMember.Volatile.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "Remove")) .IsFalse(); } }