using PkmnLib.Dynamic.Models; using PkmnLib.Plugin.Gen7.Scripts.Moves; using PkmnLib.Static; using PkmnLib.Static.Utils; namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves; /// /// Tests for the move script. /// Gen VII Bulbapedia behavior: "Moonlight restores the user's current HP based on the weather in the battle. /// During no weather it restores ½ total HP, during harsh sunlight it restores ⅔ total HP, and during other /// weather it restores ¼ total HP, rounded down." Generation VI onwards: "When used during strong winds it /// restores ½ total HP, rounded down." /// public class MoonlightTests { /// /// Creates a fully mocked test setup for Moonlight tests. Moonlight targets the user itself, so the /// returned Pokémon is used as both the move's user and the target of the secondary effect. /// private static (Moonlight moonlight, IExecutingMove move, IPokemon user) CreateTestSetup(uint maxHp, string? weatherName) { var moonlight = new Moonlight(); var move = Substitute.For(); var battle = Substitute.For(); battle.WeatherName.Returns(weatherName == null ? (StringKey?)null : new StringKey(weatherName)); var battleData = Substitute.For(); battleData.Battle.Returns(battle); var user = Substitute.For(); user.BattleData.Returns(battleData); user.BoostedStats.Returns(new StatisticSet(maxHp, 0, 0, 0, 0, 0)); move.User.Returns(user); return (moonlight, move, user); } /// /// Helper to extract the heal amount from the user's received Heal calls. /// private static uint? GetHealAmount(IPokemon user) { var call = user.ReceivedCalls().FirstOrDefault(c => c.GetMethodInfo().Name == "Heal"); return call != null ? (uint)call.GetArguments()[0]! : null; } /// /// Bulbapedia: "During no weather it restores ½ total HP ... rounded down." /// [Test, Arguments(100u, 50u), Arguments(99u, 49u), Arguments(255u, 127u)] public async Task OnSecondaryEffect_NoWeather_HealsHalfMaxHp(uint maxHp, uint expectedHeal) { // Arrange var (moonlight, move, user) = CreateTestSetup(maxHp, null); // Act moonlight.OnSecondaryEffect(move, user, 0); // Assert await Assert.That(GetHealAmount(user)!.Value).IsEqualTo(expectedHeal); } /// /// Bulbapedia: "during harsh sunlight it restores ⅔ total HP ... rounded down." /// [Test, Arguments(100u, 66u), Arguments(99u, 66u), Arguments(300u, 200u)] public async Task OnSecondaryEffect_HarshSunlight_HealsTwoThirdsMaxHp(uint maxHp, uint expectedHeal) { // Arrange var (moonlight, move, user) = CreateTestSetup(maxHp, "harsh_sunlight"); // Act moonlight.OnSecondaryEffect(move, user, 0); // Assert await Assert.That(GetHealAmount(user)!.Value).IsEqualTo(expectedHeal); } /// /// Bulbapedia: "during other weather it restores ¼ total HP, rounded down." Rain, hail and sandstorm all /// count as "other weather". /// [Test, Arguments("rain", 100u, 25u), Arguments("hail", 100u, 25u), Arguments("sandstorm", 100u, 25u), Arguments("rain", 103u, 25u), Arguments("sandstorm", 7u, 1u)] public async Task OnSecondaryEffect_OtherWeather_HealsQuarterMaxHp(string weather, uint maxHp, uint expectedHeal) { // Arrange var (moonlight, move, user) = CreateTestSetup(maxHp, weather); // Act moonlight.OnSecondaryEffect(move, user, 0); // Assert await Assert.That(GetHealAmount(user)!.Value).IsEqualTo(expectedHeal); } /// /// Bulbapedia (Generation VI onwards): "When used during strong winds it restores ½ total HP, rounded /// down." /// [Test, Arguments(100u, 50u), Arguments(99u, 49u)] public async Task OnSecondaryEffect_StrongWinds_HealsHalfMaxHp(uint maxHp, uint expectedHeal) { // Arrange var (moonlight, move, user) = CreateTestSetup(maxHp, "strong_winds"); // Act moonlight.OnSecondaryEffect(move, user, 0); // Assert await Assert.That(GetHealAmount(user)!.Value).IsEqualTo(expectedHeal); } /// /// Bulbapedia: "Moonlight restores the user's current HP" — the heal is applied to the move's user. /// [Test] public async Task OnSecondaryEffect_NoWeather_HealsTheUser() { // Arrange var (moonlight, move, user) = CreateTestSetup(100, null); // Act moonlight.OnSecondaryEffect(move, user, 0); // Assert await Assert.That(user.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "Heal")).IsTrue(); } /// /// Technical test: without battle data on the user (outside of battle) the secondary effect does nothing /// and does not throw. /// [Test] public async Task OnSecondaryEffect_UserHasNoBattleData_DoesNotHeal() { // Arrange var moonlight = new Moonlight(); var move = Substitute.For(); var user = Substitute.For(); user.BattleData.Returns((IPokemonBattleData?)null); move.User.Returns(user); // Act moonlight.OnSecondaryEffect(move, user, 0); // Assert await Assert.That(user.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "Heal")).IsFalse(); } }