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,142 @@
using PkmnLib.Dynamic.Events;
using PkmnLib.Dynamic.Models;
using PkmnLib.Dynamic.ScriptHandling;
using PkmnLib.Plugin.Gen7.Scripts.Moves;
using PkmnLib.Plugin.Gen7.Scripts.Pokemon;
using PkmnLib.Static;
using PkmnLib.Static.Utils;
namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves;
/// <summary>
/// Tests for the <see cref="Flatter"/> move script.
/// Gen VII Bulbapedia behavior: Flatter raises the target's Special Attack stat by one stage and confuses it.
/// </summary>
public class FlatterTests
{
private static (Flatter script, IExecutingMove move, IPokemon target, IScriptSet targetVolatile) CreateTestSetup()
{
var script = new Flatter();
var move = Substitute.For<IExecutingMove>();
var user = Substitute.For<IPokemon>();
move.User.Returns(user);
var target = Substitute.For<IPokemon>();
var targetVolatile = Substitute.For<IScriptSet>();
target.Volatile.Returns(targetVolatile);
return (script, move, target, targetVolatile);
}
/// <summary>
/// Helper to extract the arguments of the received ChangeStatBoost call, or null when no stat boost
/// was requested. Received-call inspection is used instead of NSubstitute argument matchers because
/// the trailing <see cref="EventBatchId"/> parameter cannot be bound by <c>Arg.Any</c> (its
/// parameterless constructor initializes a fresh id, so it never equals the matcher's default value).
/// </summary>
private static object?[]? GetStatBoostArgs(IPokemon pokemon)
{
var call = pokemon.ReceivedCalls().FirstOrDefault(c => c.GetMethodInfo().Name == "ChangeStatBoost");
return call?.GetArguments();
}
/// <summary>
/// Bulbapedia: Flatter "raises the target's Special Attack stat by one stage".
/// The boost is caused by the opponent's move, so it is not self-inflicted.
/// </summary>
[Test]
public async Task OnSecondaryEffect_Always_RaisesTargetSpecialAttackByOneStage()
{
// Arrange
var (script, move, target, _) = CreateTestSetup();
// Act
script.OnSecondaryEffect(move, target, 0);
// Assert
var args = GetStatBoostArgs(target);
await Assert.That(args).IsNotNull();
await Assert.That((Statistic)args![0]!).IsEqualTo(Statistic.SpecialAttack);
await Assert.That((sbyte)args[1]!).IsEqualTo((sbyte)1);
await Assert.That((bool)args[2]!).IsFalse(); // not self-inflicted
await Assert.That((bool)args[3]!).IsFalse(); // not forced
}
/// <summary>
/// Bulbapedia: Flatter raises the target's Special Attack "and confuses it". The confusion is applied
/// through the target's <see cref="IPokemon.Volatile"/> script set.
/// </summary>
[Test]
public async Task OnSecondaryEffect_Always_ConfusesTarget()
{
// Arrange
var (script, move, target, targetVolatile) = CreateTestSetup();
// Act
script.OnSecondaryEffect(move, target, 0);
// Assert
targetVolatile.Received(1).StackOrAdd(new StringKey("confusion"), Arg.Any<Func<Script?>>());
await Assert.That(targetVolatile.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "StackOrAdd")).IsTrue();
}
/// <summary>
/// Bulbapedia: Flatter "confuses" the target. The instantiation function passed to the volatile script
/// set creates a <see cref="Confusion"/> script.
/// </summary>
[Test]
public async Task OnSecondaryEffect_ConfusionInstantiation_CreatesConfusionScript()
{
// Arrange
var (script, move, target, targetVolatile) = CreateTestSetup();
// Act
script.OnSecondaryEffect(move, target, 0);
// Assert
var call = targetVolatile.ReceivedCalls().First(c => c.GetMethodInfo().Name == "StackOrAdd");
var instantiation = (Func<Script?>)call.GetArguments()[1]!;
await Assert.That(instantiation() is Confusion).IsTrue();
}
/// <summary>
/// Bulbapedia: "Even if the target's Special Attack is already at +6 stages, it will still become
/// confused." A substitute's <see cref="IPokemon.ChangeStatBoost"/> returns false by default (the
/// boost failed), yet the confusion is still applied.
/// </summary>
[Test]
public async Task OnSecondaryEffect_StatBoostFails_TargetStillConfused()
{
// Arrange
var (script, move, target, targetVolatile) = CreateTestSetup();
// Act
script.OnSecondaryEffect(move, target, 0);
// Assert - the unconfigured ChangeStatBoost returned false, but the target was still confused
await Assert.That(GetStatBoostArgs(target)).IsNotNull();
await Assert.That(targetVolatile.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "StackOrAdd")).IsTrue();
}
/// <summary>
/// Bulbapedia: "Even if the target is already confused, or cannot become confused due to Safeguard or
/// Own Tempo, its Special Attack will still be raised." When the volatile set refuses to add the
/// confusion script (returns null), the Special Attack boost still happens.
/// </summary>
[Test]
public async Task OnSecondaryEffect_ConfusionNotAdded_SpecialAttackStillRaised()
{
// Arrange
var (script, move, target, targetVolatile) = CreateTestSetup();
targetVolatile.StackOrAdd(Arg.Any<StringKey>(), Arg.Any<Func<Script?>>()).Returns((ScriptContainer?)null);
// Act
script.OnSecondaryEffect(move, target, 0);
// Assert
var args = GetStatBoostArgs(target);
await Assert.That(args).IsNotNull();
await Assert.That((Statistic)args![0]!).IsEqualTo(Statistic.SpecialAttack);
await Assert.That((sbyte)args[1]!).IsEqualTo((sbyte)1);
}
}