Many more tests and fixes
All checks were successful
Build / Build (push) Successful in 3m12s

This commit is contained in:
2026-08-27 17:11:12 +02:00
parent 32b3ef9c4a
commit d2a82b5fe3
204 changed files with 20255 additions and 242 deletions

View File

@@ -0,0 +1,234 @@
using PkmnLib.Dynamic.Libraries;
using PkmnLib.Dynamic.Models;
using PkmnLib.Plugin.Gen7.Scripts.Moves;
using PkmnLib.Static;
using PkmnLib.Static.Utils;
namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves;
/// <summary>
/// Tests for the <see cref="FreezeDry"/> move script.
/// Behavior is verified against the Bulbapedia page for Freeze-Dry (Generation VII behavior).
/// The effectiveness tests use the real Gen7 type chart, as the script's core behavior is rewriting
/// type effectiveness against Water-type targets.
/// </summary>
public class FreezeDryTests
{
/// <summary>
/// Creates a test setup with the real Gen7 type library, and a target in battle with the given types.
/// </summary>
private static (FreezeDry script, IExecutingMove move, IPokemon target, IDynamicLibrary library)
CreateEffectivenessSetup(params string[] targetTypes)
{
var script = new FreezeDry();
var library = LibraryHelpers.LoadLibrary();
var battle = Substitute.For<IBattle>();
battle.Library.Returns(library);
var battleData = Substitute.For<IPokemonBattleData>();
battleData.Battle.Returns(battle);
var target = Substitute.For<IPokemon>();
target.BattleData.Returns(battleData);
target.Types.Returns(targetTypes.Select(name => GetTypeId(library, name)).ToList());
var move = Substitute.For<IExecutingMove>();
var hitData = Substitute.For<IHitData>();
hitData.Type.Returns(GetTypeId(library, "ice"));
move.GetHitData(Arg.Any<IPokemon>(), Arg.Any<byte>()).Returns(hitData);
var user = Substitute.For<IPokemon>();
move.User.Returns(user);
return (script, move, target, library);
}
/// <summary>
/// Creates a fully mocked test setup for the secondary (freeze) effect tests, with a battle random
/// that reports the given result for effect chance rolls.
/// </summary>
private static (FreezeDry script, IExecutingMove move, IPokemon target, IPokemon user, IBattleRandom random)
CreateSecondaryEffectSetup(bool effectChanceResult)
{
var script = new FreezeDry();
var random = Substitute.For<IBattleRandom>();
random.EffectChance(Arg.Any<float>(), Arg.Any<IExecutingMove>(), Arg.Any<IPokemon>(), Arg.Any<byte>())
.Returns(effectChanceResult);
var battle = Substitute.For<IBattle>();
battle.Random.Returns(random);
var battleData = Substitute.For<IPokemonBattleData>();
battleData.Battle.Returns(battle);
var target = Substitute.For<IPokemon>();
target.BattleData.Returns(battleData);
var move = Substitute.For<IExecutingMove>();
var user = Substitute.For<IPokemon>();
move.User.Returns(user);
return (script, move, target, user, random);
}
/// <summary>
/// Helper to resolve a type identifier from the loaded Gen7 library.
/// </summary>
private static TypeIdentifier GetTypeId(IDynamicLibrary library, string name)
{
library.StaticLibrary.Types.TryGetTypeIdentifier(name, out var type);
return type;
}
/// <summary>
/// Bulbapedia: "Freeze-Dry will always deal supereffective (x2) damage to the Water-type, even during
/// Inverse Battles or if its type is changed."
/// Against a pure Water-type target, the effectiveness is always rewritten to exactly 2, regardless of
/// the naturally calculated incoming effectiveness.
/// </summary>
[Test, Arguments(0.5f), Arguments(1f), Arguments(2f)]
public async Task ChangeEffectiveness_PureWaterTarget_EffectivenessAlwaysBecomesDouble(float initialEffectiveness)
{
// Arrange
var (script, move, target, _) = CreateEffectivenessSetup("water");
var effectiveness = initialEffectiveness;
// Act
script.ChangeEffectiveness(move, target, 0, ref effectiveness);
// Assert
await Assert.That(effectiveness).IsEqualTo(2f);
}
/// <summary>
/// Bulbapedia: "Freeze-Dry will always deal supereffective (x2) damage to the Water-type".
/// The x2 replaces only Ice's normal matchup against the Water type; the move's regular effectiveness
/// against the target's other type still applies. For example, against a Water/Flying Pokémon such as
/// Gyarados, Freeze-Dry is 4x effective (2x from Water, 2x from Ice against Flying).
/// </summary>
[Test, Arguments("flying", 4f), Arguments("grass", 4f), Arguments("ground", 4f), Arguments("ice", 1f),
Arguments("steel", 1f)]
public async Task ChangeEffectiveness_DualTypeWaterTarget_DoubleAgainstWaterTimesOtherTypeEffectiveness(
string otherType, float expected)
{
// Arrange
var (script, move, target, library) = CreateEffectivenessSetup("water", otherType);
// Start from the naturally calculated effectiveness of an Ice-type move against the target.
var effectiveness = library.StaticLibrary.Types.GetEffectiveness(GetTypeId(library, "ice"), target.Types);
// Act
script.ChangeEffectiveness(move, target, 0, ref effectiveness);
// Assert
await Assert.That(effectiveness).IsEqualTo(expected);
}
/// <summary>
/// Bulbapedia: the guaranteed super effectiveness applies "to the Water-type". Against a target without
/// the Water type, the effectiveness must be left untouched.
/// </summary>
[Test]
public async Task ChangeEffectiveness_NonWaterTarget_EffectivenessUnchanged()
{
// Arrange
var (script, move, target, _) = CreateEffectivenessSetup("grass");
var effectiveness = 2f;
// Act
script.ChangeEffectiveness(move, target, 0, ref effectiveness);
// Assert
await Assert.That(effectiveness).IsEqualTo(2f);
}
/// <summary>
/// If the target has no <see cref="IPokemon.BattleData"/>, the script cannot resolve the type library,
/// so the effectiveness must be left untouched.
/// </summary>
[Test]
public async Task ChangeEffectiveness_NullBattleData_EffectivenessUnchanged()
{
// Arrange
var (script, move, target, _) = CreateEffectivenessSetup("water");
target.BattleData.Returns((IPokemonBattleData?)null);
var effectiveness = 0.5f;
// Act
script.ChangeEffectiveness(move, target, 0, ref effectiveness);
// Assert
await Assert.That(effectiveness).IsEqualTo(0.5f);
}
/// <summary>
/// Bulbapedia (Generation VI to VIII): "Freeze-Dry has a 10% chance of freezing the target."
/// When the effect chance roll succeeds, the target is frozen, with the user as the origin.
/// </summary>
[Test]
public async Task OnSecondaryEffect_EffectChanceSucceeds_TargetIsFrozen()
{
// Arrange
var (script, move, target, user, _) = CreateSecondaryEffectSetup(true);
// Act
script.OnSecondaryEffect(move, target, 0);
// Assert
var call = target.ReceivedCalls().FirstOrDefault(c => c.GetMethodInfo().Name == "SetStatus");
await Assert.That(call).IsNotNull();
await Assert.That((StringKey)call!.GetArguments()[0]!).IsEqualTo(new StringKey("frozen"));
await Assert.That(ReferenceEquals(call.GetArguments()[1], user)).IsTrue();
}
/// <summary>
/// Bulbapedia (Generation VI to VIII): "Freeze-Dry has a 10% chance of freezing the target."
/// When the effect chance roll fails, no status may be set.
/// </summary>
[Test]
public async Task OnSecondaryEffect_EffectChanceFails_TargetNotFrozen()
{
// Arrange
var (script, move, target, _, _) = CreateSecondaryEffectSetup(false);
// Act
script.OnSecondaryEffect(move, target, 0);
// Assert
await Assert.That(target.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "SetStatus")).IsFalse();
}
/// <summary>
/// Bulbapedia (Generation VI to VIII): "Freeze-Dry has a 10% chance of freezing the target."
/// The effect chance roll must be made with a 10% chance.
/// </summary>
[Test]
public async Task OnSecondaryEffect_Always_RollsTenPercentEffectChance()
{
// Arrange
var (script, move, target, _, random) = CreateSecondaryEffectSetup(false);
// Act
script.OnSecondaryEffect(move, target, 0);
// Assert
var call = random.ReceivedCalls().FirstOrDefault(c => c.GetMethodInfo().Name == "EffectChance");
await Assert.That(call).IsNotNull();
await Assert.That((float)call!.GetArguments()[0]!).IsEqualTo(10f);
}
/// <summary>
/// If the target has no <see cref="IPokemon.BattleData"/>, no effect chance can be rolled, so the
/// target may not be frozen.
/// </summary>
[Test]
public async Task OnSecondaryEffect_NullBattleData_TargetNotFrozen()
{
// Arrange
var (script, move, target, _, _) = CreateSecondaryEffectSetup(true);
target.BattleData.Returns((IPokemonBattleData?)null);
// Act
script.OnSecondaryEffect(move, target, 0);
// Assert
await Assert.That(target.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "SetStatus")).IsFalse();
}
}