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,156 @@
using PkmnLib.Dynamic.Events;
using PkmnLib.Dynamic.Models;
using PkmnLib.Plugin.Gen7.Scripts.Moves;
using PkmnLib.Static;
namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves;
/// <summary>
/// Tests for the <see cref="Memento"/> move script.
/// Gen VII Bulbapedia behavior: "Memento causes the user to faint and in return lowers the Attack and
/// Special Attack stat of the target by two stages each." From Generation V onward the user does not faint
/// if the move fails (no target, miss, blocked by a substitute) - that part is engine behavior, since
/// <see cref="Memento.OnSecondaryEffect"/> only runs when the move successfully hits.
/// </summary>
public class MementoTests
{
private static (Memento script, IExecutingMove move, IPokemon target, IPokemon user) CreateTestSetup()
{
var script = new Memento();
var move = Substitute.For<IExecutingMove>();
var target = Substitute.For<IPokemon>();
var user = Substitute.For<IPokemon>();
move.User.Returns(user);
return (script, move, target, user);
}
/// <summary>
/// Helper to extract the arguments of the received ChangeStatBoost call for the given stat, or null
/// when no boost for that stat 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>.
/// </summary>
private static object?[]? GetStatBoostArgs(IPokemon pokemon, Statistic stat) =>
pokemon.ReceivedCalls().Where(c => c.GetMethodInfo().Name == "ChangeStatBoost").Select(c => c.GetArguments())
.FirstOrDefault(args => (Statistic)args[0]! == stat);
/// <summary>
/// Bulbapedia: "Memento causes the user to faint and in return lowers the Attack and Special Attack stat
/// of the target by two stages each."
/// The Attack drop is caused by the opponent's move, so it is not self-inflicted and not forced.
/// </summary>
[Test]
public async Task OnSecondaryEffect_Hit_LowersTargetAttackByTwoStages()
{
// Arrange
var (script, move, target, _) = CreateTestSetup();
// Act
script.OnSecondaryEffect(move, target, 0);
// Assert
var args = GetStatBoostArgs(target, Statistic.Attack);
await Assert.That(args).IsNotNull();
await Assert.That((sbyte)args![1]!).IsEqualTo((sbyte)-2);
await Assert.That((bool)args[2]!).IsFalse();
await Assert.That((bool)args[3]!).IsFalse();
}
/// <summary>
/// Bulbapedia: "Memento causes the user to faint and in return lowers the Attack and Special Attack stat
/// of the target by two stages each."
/// The Special Attack drop is caused by the opponent's move, so it is not self-inflicted and not forced.
/// </summary>
[Test]
public async Task OnSecondaryEffect_Hit_LowersTargetSpecialAttackByTwoStages()
{
// Arrange
var (script, move, target, _) = CreateTestSetup();
// Act
script.OnSecondaryEffect(move, target, 0);
// Assert
var args = GetStatBoostArgs(target, Statistic.SpecialAttack);
await Assert.That(args).IsNotNull();
await Assert.That((sbyte)args![1]!).IsEqualTo((sbyte)-2);
await Assert.That((bool)args[2]!).IsFalse();
await Assert.That((bool)args[3]!).IsFalse();
}
/// <summary>
/// Bulbapedia: "Memento causes the user to faint and in return lowers the Attack and Special Attack stat
/// of the target by two stages each."
/// Only those two stats are lowered; no other stat changes are requested.
/// </summary>
[Test]
public async Task OnSecondaryEffect_Hit_OnlyLowersAttackAndSpecialAttack()
{
// Arrange
var (script, move, target, _) = CreateTestSetup();
// Act
script.OnSecondaryEffect(move, target, 0);
// Assert
var statBoostCalls = target.ReceivedCalls().Count(c => c.GetMethodInfo().Name == "ChangeStatBoost");
await Assert.That(statBoostCalls).IsEqualTo(2);
}
/// <summary>
/// Bulbapedia: "Memento causes the user to faint".
/// The faint is not move damage, so it uses <see cref="DamageSource.Misc"/>.
/// </summary>
[Test]
public async Task OnSecondaryEffect_Hit_UserFaints()
{
// Arrange
var (script, move, target, user) = CreateTestSetup();
// Act
script.OnSecondaryEffect(move, target, 0);
// Assert
var faintCall = user.ReceivedCalls().FirstOrDefault(c => c.GetMethodInfo().Name == "Faint");
await Assert.That(faintCall).IsNotNull();
await Assert.That((DamageSource)faintCall!.GetArguments()[0]!).IsEqualTo(DamageSource.Misc);
}
/// <summary>
/// Bulbapedia (Generation V onward): "the user faints if it successfully hits but cannot lower stats due
/// to abilities like Clear Body or stats already at -6."
/// A substitute's <see cref="IPokemon.ChangeStatBoost"/> returns false by default (the stat drops were
/// prevented), but the user must still faint.
/// </summary>
[Test]
public async Task OnSecondaryEffect_StatDropsPrevented_UserStillFaints()
{
// Arrange
var (script, move, target, user) = CreateTestSetup();
// Act - the unconfigured ChangeStatBoost calls return false, as if blocked by Clear Body
script.OnSecondaryEffect(move, target, 0);
// Assert
await Assert.That(user.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "Faint")).IsTrue();
}
/// <summary>
/// Technical test: both stat drops are batched under the same <see cref="EventBatchId"/> so they are
/// shown as a single event to the client.
/// </summary>
[Test]
public async Task OnSecondaryEffect_Hit_StatDropsShareEventBatch()
{
// Arrange
var (script, move, target, _) = CreateTestSetup();
// Act
script.OnSecondaryEffect(move, target, 0);
// Assert
var attackBatch = (EventBatchId)GetStatBoostArgs(target, Statistic.Attack)![4]!;
var specialAttackBatch = (EventBatchId)GetStatBoostArgs(target, Statistic.SpecialAttack)![4]!;
await Assert.That(attackBatch).IsEqualTo(specialAttackBatch);
}
}