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();
}
}