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,182 @@
using PkmnLib.Dynamic.Events;
using PkmnLib.Dynamic.Models;
using PkmnLib.Plugin.Gen7.Scripts.Moves;
namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves;
/// <summary>
/// Tests for the <see cref="Magnitude"/> move script.
/// Gen VII Bulbapedia behavior: "Magnitude's power varies based on a randomly selected 'magnitude' value
/// ranging from 4 to 10, with 4 having the least power and 10 having the greatest."
/// The doubling of damage against a Pokémon in the semi-invulnerable turn of Dig and the halving under
/// Grassy Terrain are handled outside this script and are not tested here.
/// </summary>
public class MagnitudeTests
{
private static (Magnitude script, IExecutingMove move, IPokemon target, IPokemon user, EventHook eventHook)
CreateTestSetup(int randomValue)
{
var script = new Magnitude();
var random = Substitute.For<IBattleRandom>();
random.GetInt(0, 100).Returns(randomValue);
var eventHook = new EventHook();
var battle = Substitute.For<IBattle>();
battle.Random.Returns(random);
battle.EventHook.Returns(eventHook);
var battleData = Substitute.For<IPokemonBattleData>();
battleData.Battle.Returns(battle);
var user = Substitute.For<IPokemon>();
user.BattleData.Returns(battleData);
var move = Substitute.For<IExecutingMove>();
move.User.Returns(user);
var target = Substitute.For<IPokemon>();
return (script, move, target, user, eventHook);
}
/// <summary>
/// Bulbapedia magnitude→power table: 4→10, 5→30, 6→50, 7→70, 8→90, 9→110, 10→150.
/// Each row exercises a boundary of a random-percentage bracket, verifying both edges of every
/// magnitude's probability range (5%, 10%, 20%, 30%, 20%, 10%, 5%).
/// </summary>
[Test, Arguments(0, 10), Arguments(4, 10), Arguments(5, 30), Arguments(14, 30), Arguments(15, 50),
Arguments(34, 50), Arguments(35, 70), Arguments(64, 70), Arguments(65, 90), Arguments(84, 90), Arguments(85, 110),
Arguments(94, 110), Arguments(95, 150), Arguments(99, 150)]
// magnitude 4 bracket start
// magnitude 4 bracket end
// magnitude 5 bracket start
// magnitude 5 bracket end
// magnitude 6 bracket start
// magnitude 6 bracket end
// magnitude 7 bracket start
// magnitude 7 bracket end
// magnitude 8 bracket start
// magnitude 8 bracket end
// magnitude 9 bracket start
// magnitude 9 bracket end
// magnitude 10 bracket start
// magnitude 10 bracket end
public async Task ChangeBasePower_RandomRoll_MapsToExpectedBasePower(int randomValue, int expectedPower)
{
// Arrange
var (script, move, target, _, _) = CreateTestSetup(randomValue);
ushort basePower = 1;
// Act
script.ChangeBasePower(move, target, 0, ref basePower);
// Assert
await Assert.That(basePower).IsEqualTo((ushort)expectedPower);
}
/// <summary>
/// Bulbapedia probability table: magnitude 4 has 5% probability, 5 has 10%, 6 has 20%, 7 has 30%,
/// 8 has 20%, 9 has 10% and 10 has 5%. Rolling every possible random percentage (099) exactly once
/// must produce each power in exactly that proportion.
/// </summary>
[Test]
public async Task ChangeBasePower_AllRandomRolls_MatchBulbapediaProbabilities()
{
// Arrange
var powerCounts = new Dictionary<ushort, int>();
// Act - roll every possible random percentage once
for (var roll = 0; roll < 100; roll++)
{
var (script, move, target, _, _) = CreateTestSetup(roll);
ushort basePower = 1;
script.ChangeBasePower(move, target, 0, ref basePower);
powerCounts[basePower] = powerCounts.GetValueOrDefault(basePower) + 1;
}
// Assert - counts out of 100 equal the Bulbapedia percentages
await Assert.That(powerCounts.GetValueOrDefault((ushort)10)).IsEqualTo(5);
await Assert.That(powerCounts.GetValueOrDefault((ushort)30)).IsEqualTo(10);
await Assert.That(powerCounts.GetValueOrDefault((ushort)50)).IsEqualTo(20);
await Assert.That(powerCounts.GetValueOrDefault((ushort)70)).IsEqualTo(30);
await Assert.That(powerCounts.GetValueOrDefault((ushort)90)).IsEqualTo(20);
await Assert.That(powerCounts.GetValueOrDefault((ushort)110)).IsEqualTo(10);
await Assert.That(powerCounts.GetValueOrDefault((ushort)150)).IsEqualTo(5);
}
/// <summary>
/// Bulbapedia: the power is based on "a randomly selected 'magnitude' value ranging from 4 to 10" —
/// the selected magnitude is announced through a <see cref="DialogEvent"/> so the front-end can show
/// it (in the games: "Magnitude &lt;value&gt;!").
/// </summary>
[Test, Arguments(0, 4), Arguments(5, 5), Arguments(15, 6), Arguments(35, 7), Arguments(65, 8), Arguments(85, 9),
Arguments(95, 10)]
public async Task ChangeBasePower_RandomRoll_FiresDialogEventWithSelectedMagnitude(int randomValue,
int expectedMagnitude)
{
// Arrange
var (script, move, target, _, eventHook) = CreateTestSetup(randomValue);
DialogEvent? capturedEvent = null;
eventHook.Handler += (_, args) =>
{
if (args is DialogEvent dialogEvent)
capturedEvent = dialogEvent;
};
ushort basePower = 1;
// Act
script.ChangeBasePower(move, target, 0, ref basePower);
// Assert
await Assert.That(capturedEvent).IsNotNull();
await Assert.That(capturedEvent!.Message).IsEqualTo("magnitude");
await Assert.That((int)capturedEvent.Parameters!["magnitude"]).IsEqualTo(expectedMagnitude);
}
/// <summary>
/// Bulbapedia: the magnitude announcement concerns the move's user and target — the fired
/// <see cref="DialogEvent"/> carries both so the front-end can format its message.
/// </summary>
[Test]
public async Task ChangeBasePower_RandomRoll_DialogEventContainsUserAndTarget()
{
// Arrange
var (script, move, target, user, eventHook) = CreateTestSetup(50);
DialogEvent? capturedEvent = null;
eventHook.Handler += (_, args) =>
{
if (args is DialogEvent dialogEvent)
capturedEvent = dialogEvent;
};
ushort basePower = 1;
// Act
script.ChangeBasePower(move, target, 0, ref basePower);
// Assert
await Assert.That(capturedEvent).IsNotNull();
await Assert.That((IPokemon)capturedEvent!.Parameters!["user"]).IsEqualTo(user);
await Assert.That((IPokemon)capturedEvent.Parameters!["target"]).IsEqualTo(target);
}
/// <summary>
/// Technical test: when the user has no <see cref="IPokemon.BattleData"/> (it is not in a battle),
/// no random roll can be made and the base power is left unchanged.
/// </summary>
[Test]
public async Task ChangeBasePower_NullBattleData_BasePowerUnchanged()
{
// Arrange
var script = new Magnitude();
var user = Substitute.For<IPokemon>();
user.BattleData.Returns((IPokemonBattleData?)null);
var move = Substitute.For<IExecutingMove>();
move.User.Returns(user);
ushort basePower = 1;
// Act
script.ChangeBasePower(move, Substitute.For<IPokemon>(), 0, ref basePower);
// Assert
await Assert.That(basePower).IsEqualTo((ushort)1);
}
}