Move all battle state from IPokemon to an ephemeral IBattlePokemon wrapper

This commit is contained in:
2026-08-28 15:20:26 +02:00
parent 942be8eaeb
commit 8a2733a0a9
859 changed files with 4408 additions and 5331 deletions

View File

@@ -11,9 +11,9 @@ public class AcrobaticsTests
{
// Arrange
var move = Substitute.For<IExecutingMove>();
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
ushort basePower = 10;
var user = Substitute.For<IPokemon>();
var user = Substitute.For<IBattlePokemon>();
user.HeldItem.Returns((IItem?)null);
move.User.Returns(user);
var acrobatics = new Acrobatics();
@@ -30,9 +30,9 @@ public class AcrobaticsTests
{
// Arrange
var move = Substitute.For<IExecutingMove>();
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
ushort basePower = 10;
var user = Substitute.For<IPokemon>();
var user = Substitute.For<IBattlePokemon>();
user.HeldItem.Returns(Substitute.For<IItem>());
move.User.Returns(user);
var acrobatics = new Acrobatics();
@@ -49,9 +49,9 @@ public class AcrobaticsTests
{
// Arrange
var move = Substitute.For<IExecutingMove>();
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
ushort basePower = ushort.MaxValue - 100;
var user = Substitute.For<IPokemon>();
var user = Substitute.For<IBattlePokemon>();
move.User.Returns(user);
user.HeldItem.Returns((IItem?)null);
var acrobatics = new Acrobatics();

View File

@@ -13,24 +13,22 @@ namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves;
/// </summary>
public class AcupressureTests
{
private static (Acupressure script, IExecutingMove move, IPokemon target, IBattleRandom random, IHitData hitData)
CreateTestSetup()
private static (Acupressure script, IExecutingMove move, IBattlePokemon target, IBattleRandom random, IHitData
hitData) CreateTestSetup()
{
var script = new Acupressure();
var move = Substitute.For<IExecutingMove>();
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
var hitData = Substitute.For<IHitData>();
move.GetHitData(target, 0).Returns(hitData);
var random = Substitute.For<IBattleRandom>();
var battle = Substitute.For<IBattle>();
battle.Random.Returns(random);
var battleData = Substitute.For<IPokemonBattleData>();
battleData.Battle.Returns(battle);
var user = Substitute.For<IPokemon>();
user.BattleData.Returns(battleData);
var user = Substitute.For<IBattlePokemon>();
move.User.Returns(user);
user.Battle.Returns(battle);
target.StatBoost.Returns(new StatBoostStatisticSet());
@@ -40,7 +38,7 @@ public class AcupressureTests
/// <summary>
/// Helper to extract the stat argument of a ChangeStatBoost call received by the target.
/// </summary>
private static object?[]? GetStatBoostCallArgs(IPokemon target)
private static object?[]? GetStatBoostCallArgs(IBattlePokemon target)
{
var call = target.ReceivedCalls().FirstOrDefault(c => c.GetMethodInfo().Name == "ChangeStatBoost");
return call?.GetArguments();

View File

@@ -12,7 +12,7 @@ namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves;
/// </summary>
public class AfterYouTests
{
private static IMoveChoice CreateChoice(IPokemon user, uint speed)
private static IMoveChoice CreateChoice(IBattlePokemon user, uint speed)
{
var choice = Substitute.For<IMoveChoice>();
choice.User.Returns(user);
@@ -25,16 +25,14 @@ public class AfterYouTests
var script = new AfterYou();
var move = Substitute.For<IExecutingMove>();
var hitData = Substitute.For<IHitData>();
move.GetHitData(Arg.Any<IPokemon>(), Arg.Any<byte>()).Returns(hitData);
move.GetHitData(Arg.Any<IBattlePokemon>(), Arg.Any<byte>()).Returns(hitData);
var battle = Substitute.For<IBattle>();
battle.ChoiceQueue.Returns(queue);
var battleData = Substitute.For<IPokemonBattleData>();
battleData.Battle.Returns(battle);
var user = Substitute.For<IPokemon>();
user.BattleData.Returns(battleData);
var user = Substitute.For<IBattlePokemon>();
move.User.Returns(user);
user.Battle.Returns(battle);
return (script, move, hitData);
}
@@ -47,9 +45,9 @@ public class AfterYouTests
public async Task OnSecondaryEffect_TargetLaterInQueue_TargetMovesNext()
{
// Arrange
var user = Substitute.For<IPokemon>();
var target = Substitute.For<IPokemon>();
var other = Substitute.For<IPokemon>();
var user = Substitute.For<IBattlePokemon>();
var target = Substitute.For<IBattlePokemon>();
var other = Substitute.For<IBattlePokemon>();
// Sorted by speed: user (100), other (75), target (50)
var queue = new BattleChoiceQueue([
CreateChoice(user, 100), CreateChoice(other, 75), CreateChoice(target, 50),
@@ -75,8 +73,8 @@ public class AfterYouTests
public void OnSecondaryEffect_TargetAlreadyMoved_Fails()
{
// Arrange
var user = Substitute.For<IPokemon>();
var target = Substitute.For<IPokemon>();
var user = Substitute.For<IBattlePokemon>();
var target = Substitute.For<IBattlePokemon>();
// Sorted by speed: target (100), user (50)
var queue = new BattleChoiceQueue([
CreateChoice(target, 100), CreateChoice(user, 50),
@@ -103,8 +101,8 @@ public class AfterYouTests
public void OnSecondaryEffect_TargetAlreadyNext_Fails()
{
// Arrange
var user = Substitute.For<IPokemon>();
var target = Substitute.For<IPokemon>();
var user = Substitute.For<IBattlePokemon>();
var target = Substitute.For<IBattlePokemon>();
// Sorted by speed: user (100), target (50)
var queue = new BattleChoiceQueue([
CreateChoice(user, 100), CreateChoice(target, 50),
@@ -132,7 +130,7 @@ public class AfterYouTests
var (script, move, hitData) = CreateTestSetup(null);
// Act
script.OnSecondaryEffect(move, Substitute.For<IPokemon>(), 0);
script.OnSecondaryEffect(move, Substitute.For<IBattlePokemon>(), 0);
// Assert
hitData.DidNotReceive().Fail();

View File

@@ -15,9 +15,9 @@ namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves;
/// </summary>
public class AssistTests
{
private static IPokemon CreatePokemonWithMoves(params string[] moveNames)
private static IBattlePokemon CreatePokemonWithMoves(params string[] moveNames)
{
var pokemon = Substitute.For<IPokemon>();
var pokemon = Substitute.For<IBattlePokemon>();
var moves = moveNames.Select(name =>
{
var learned = Substitute.For<ILearnedMove>();
@@ -30,27 +30,23 @@ public class AssistTests
return pokemon;
}
private static (Assist script, IMoveChoice choice, IPokemon user, IBattleRandom random) CreateTestSetup(
params IPokemon?[] otherPartyMembers)
private static (Assist script, IMoveChoice choice, IBattlePokemon user, IBattleRandom random) CreateTestSetup(
params IBattlePokemon?[] otherPartyMembers)
{
var script = new Assist();
var user = CreatePokemonWithMoves("tackle");
var members = new List<IPokemon?> { user };
var members = new List<IBattlePokemon?> { user };
members.AddRange(otherPartyMembers);
var party = Substitute.For<IPokemonParty>();
party.GetEnumerator().Returns(_ => members.GetEnumerator());
var battleParty = Substitute.For<IBattleParty>();
battleParty.Party.Returns(party);
battleParty.BattlePokemon.Returns(members);
var random = Substitute.For<IBattleRandom>();
var battle = Substitute.For<IBattle>();
battle.Parties.Returns(new[] { battleParty });
battle.Random.Returns(random);
var battleData = Substitute.For<IPokemonBattleData>();
battleData.Battle.Returns(battle);
user.BattleData.Returns(battleData);
user.Battle.Returns(battle);
var choice = Substitute.For<IMoveChoice>();
choice.User.Returns(user);
@@ -180,26 +176,4 @@ public class AssistTests
await Assert.That(moveName).IsEqualTo(new StringKey("growl"));
choice.DidNotReceive().Fail();
}
/// <summary>
/// Technical test: outside of battle (no battle data) the script returns without failing the choice.
/// </summary>
[Test]
public async Task ChangeMove_NoBattleData_DoesNothing()
{
// Arrange
var script = new Assist();
var user = Substitute.For<IPokemon>();
user.BattleData.Returns((IPokemonBattleData?)null);
var choice = Substitute.For<IMoveChoice>();
choice.User.Returns(user);
StringKey moveName = "assist";
// Act
script.ChangeMove(choice, ref moveName);
// Assert
choice.DidNotReceive().Fail();
await Assert.That(moveName).IsEqualTo(new StringKey("assist"));
}
}

View File

@@ -15,12 +15,12 @@ namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves;
/// </summary>
public class AttractTests
{
private static (Attract script, IExecutingMove move, IPokemon target, IScriptSet targetVolatile, IHitData hitData)
CreateTestSetup(Gender userGender, Gender targetGender)
private static (Attract script, IExecutingMove move, IBattlePokemon target, IScriptSet targetVolatile, IHitData
hitData) CreateTestSetup(Gender userGender, Gender targetGender)
{
var script = new Attract();
var move = Substitute.For<IExecutingMove>();
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
var hitData = Substitute.For<IHitData>();
move.GetHitData(target, 0).Returns(hitData);
@@ -28,7 +28,7 @@ public class AttractTests
target.Volatile.Returns(targetVolatile);
target.Gender.Returns(targetGender);
var user = Substitute.For<IPokemon>();
var user = Substitute.For<IBattlePokemon>();
user.Gender.Returns(userGender);
move.User.Returns(user);

View File

@@ -31,12 +31,12 @@ public class AuroraVeilTests
}
}
private static (AuroraVeil script, IExecutingMove move, IPokemon target, IPokemon user, IScriptSet sideScripts,
IHitData hitData) CreateTestSetup(StringKey? weather)
private static (AuroraVeil script, IExecutingMove move, IBattlePokemon target, IBattlePokemon user, IScriptSet
sideScripts, IHitData hitData) CreateTestSetup(StringKey? weather)
{
var script = new AuroraVeil();
var move = Substitute.For<IExecutingMove>();
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
var hitData = Substitute.For<IHitData>();
move.GetHitData(target, 0).Returns(hitData);
@@ -51,13 +51,10 @@ public class AuroraVeilTests
side.VolatileScripts.Returns(sideScripts);
battle.Sides.Returns(new[] { side });
var battleData = Substitute.For<IPokemonBattleData>();
battleData.Battle.Returns(battle);
battleData.SideIndex.Returns((byte)0);
var user = Substitute.For<IPokemon>();
user.BattleData.Returns(battleData);
var user = Substitute.For<IBattlePokemon>();
user.GetScripts().Returns(_ => new ScriptIterator(new List<IEnumerable<ScriptContainer>>()));
user.SideIndex.Returns((byte)0);
user.Battle.Returns(battle);
move.User.Returns(user);
return (script, move, target, user, sideScripts, hitData);
@@ -154,25 +151,4 @@ public class AuroraVeilTests
var effect = (AuroraVeilEffect)((Func<Script?>)call.GetArguments()[1]!)()!;
await Assert.That(effect.NumberOfTurns).IsEqualTo(8);
}
/// <summary>
/// Technical test: outside of battle (no battle data) the script returns without throwing.
/// </summary>
[Test]
public async Task OnSecondaryEffect_NoBattleData_DoesNothing()
{
// Arrange
var script = new AuroraVeil();
var move = Substitute.For<IExecutingMove>();
var target = Substitute.For<IPokemon>();
var user = Substitute.For<IPokemon>();
user.BattleData.Returns((IPokemonBattleData?)null);
move.User.Returns(user);
// Act
script.OnSecondaryEffect(move, target, 0);
// Assert - no hit data was touched
await Assert.That(move.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "GetHitData")).IsFalse();
}
}

View File

@@ -17,8 +17,8 @@ namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves;
/// </summary>
public class AutotomizeTests
{
private static (Autotomize script, IExecutingMove move, IPokemon user, IScriptSet userVolatile, EventHook eventHook)
CreateTestSetup(float weightInKg, bool speedRaiseSucceeds, AutotomizeEffect? existingEffect = null)
private static (Autotomize script, IExecutingMove move, IBattlePokemon user, IScriptSet userVolatile, EventHook
eventHook) CreateTestSetup(float weightInKg, bool speedRaiseSucceeds, AutotomizeEffect? existingEffect = null)
{
var script = new Autotomize();
var move = Substitute.For<IExecutingMove>();
@@ -26,15 +26,13 @@ public class AutotomizeTests
var eventHook = new EventHook();
var battle = Substitute.For<IBattle>();
battle.EventHook.Returns(eventHook);
var battleData = Substitute.For<IPokemonBattleData>();
battleData.Battle.Returns(battle);
var userVolatile = Substitute.For<IScriptSet>();
userVolatile.Get<AutotomizeEffect>().Returns(existingEffect);
var user = Substitute.For<IPokemon>();
user.BattleData.Returns(battleData);
var user = Substitute.For<IBattlePokemon>();
user.Volatile.Returns(userVolatile);
user.Battle.Returns(battle);
user.WeightInKg.Returns(weightInKg);
user.ChangeStatBoost(Arg.Any<Statistic>(), Arg.Any<sbyte>(), Arg.Any<bool>(), Arg.Any<bool>())
.Returns(speedRaiseSucceeds);
@@ -56,7 +54,7 @@ public class AutotomizeTests
var (script, move, user, _, _) = CreateTestSetup(100f, true);
// Act
script.OnSecondaryEffect(move, Substitute.For<IPokemon>(), 0);
script.OnSecondaryEffect(move, Substitute.For<IBattlePokemon>(), 0);
// Assert
user.Received(1).ChangeStatBoost(Statistic.Speed, 2, true, false);
@@ -79,7 +77,7 @@ public class AutotomizeTests
};
// Act
script.OnSecondaryEffect(move, Substitute.For<IPokemon>(), 0);
script.OnSecondaryEffect(move, Substitute.For<IBattlePokemon>(), 0);
// Assert
await Assert.That(ReceivedStackOrAdd(userVolatile)).IsTrue();
@@ -100,7 +98,7 @@ public class AutotomizeTests
eventHook.Handler += (_, _) => eventFired = true;
// Act
script.OnSecondaryEffect(move, Substitute.For<IPokemon>(), 0);
script.OnSecondaryEffect(move, Substitute.For<IBattlePokemon>(), 0);
// Assert
await Assert.That(ReceivedStackOrAdd(userVolatile)).IsFalse();
@@ -119,7 +117,7 @@ public class AutotomizeTests
var (script, move, _, userVolatile, _) = CreateTestSetup(50f, true);
// Act
script.OnSecondaryEffect(move, Substitute.For<IPokemon>(), 0);
script.OnSecondaryEffect(move, Substitute.For<IBattlePokemon>(), 0);
// Assert
await Assert.That(ReceivedStackOrAdd(userVolatile)).IsTrue();
@@ -140,7 +138,7 @@ public class AutotomizeTests
var (script, move, _, userVolatile, _) = CreateTestSetup(50f, true, existingEffect);
// Act
script.OnSecondaryEffect(move, Substitute.For<IPokemon>(), 0);
script.OnSecondaryEffect(move, Substitute.For<IBattlePokemon>(), 0);
// Assert - the weight should still be reduced (to the minimum)
await Assert.That(ReceivedStackOrAdd(userVolatile)).IsTrue();
@@ -160,7 +158,7 @@ public class AutotomizeTests
eventHook.Handler += (_, _) => eventFired = true;
// Act
script.OnSecondaryEffect(move, Substitute.For<IPokemon>(), 0);
script.OnSecondaryEffect(move, Substitute.For<IBattlePokemon>(), 0);
// Assert
user.Received(1).ChangeStatBoost(Statistic.Speed, 2, true, false);

View File

@@ -19,12 +19,12 @@ public class BanefulBunkerTests
/// <see cref="ProtectionScript.OnSecondaryEffect"/>. The <c>target</c> of the secondary effect is the
/// Pokémon using Baneful Bunker itself, as the move is self-targeted.
/// </summary>
private static (BanefulBunker script, IExecutingMove move, IPokemon target, IHitData hitData, IScriptSet volatileSet
) CreateProtectSetup(bool userMovesLast, float randomRoll)
private static (BanefulBunker script, IExecutingMove move, IBattlePokemon target, IHitData hitData, IScriptSet
volatileSet ) CreateProtectSetup(bool userMovesLast, float randomRoll)
{
var script = new BanefulBunker();
var move = Substitute.For<IExecutingMove>();
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
var hitData = Substitute.For<IHitData>();
move.GetHitData(target, 0).Returns(hitData);
@@ -38,9 +38,7 @@ public class BanefulBunkerTests
battle.ChoiceQueue.Returns(queue);
battle.Random.Returns(random);
var battleData = Substitute.For<IPokemonBattleData>();
battleData.Battle.Returns(battle);
target.BattleData.Returns(battleData);
target.Battle.Returns(battle);
// Give the mock a real script iterator (used by the volatile add hook) and a real volatile script set.
target.GetScripts().Returns(_ => new ScriptIterator(Array.Empty<IEnumerable<ScriptContainer>>()));
@@ -54,12 +52,12 @@ public class BanefulBunkerTests
/// Creates a fully mocked setup for driving <see cref="BanefulBunkerEffect.BlockIncomingHit"/>, the
/// volatile script that <see cref="BanefulBunker"/> attaches to its user.
/// </summary>
private static (BanefulBunkerEffect effect, IExecutingMove move, IPokemon target, IPokemon attacker)
private static (BanefulBunkerEffect effect, IExecutingMove move, IBattlePokemon target, IBattlePokemon attacker)
CreateBlockSetup(bool isContact, bool hasProtectFlag, MoveCategory category = MoveCategory.Physical)
{
var effect = new BanefulBunkerEffect();
var move = Substitute.For<IExecutingMove>();
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
var hitData = Substitute.For<IHitData>();
hitData.IsContact.Returns(isContact);
move.GetHitData(target, 0).Returns(hitData);
@@ -69,19 +67,17 @@ public class BanefulBunkerTests
useMove.Category.Returns(category);
move.UseMove.Returns(useMove);
var attacker = Substitute.For<IPokemon>();
var attacker = Substitute.For<IBattlePokemon>();
attacker.GetScripts().Returns(_ => new ScriptIterator(Array.Empty<IEnumerable<ScriptContainer>>()));
move.User.Returns(attacker);
target.BattleData.Returns(Substitute.For<IPokemonBattleData>());
return (effect, move, target, attacker);
}
/// <summary>
/// Helper to extract the status name from a Pokémon's received <see cref="IPokemon.SetStatus"/> calls.
/// Helper to extract the status name from a Pokémon's received <see cref="IBattlePokemon.SetStatus"/> calls.
/// </summary>
private static string? GetStatusSet(IPokemon pokemon)
private static string? GetStatusSet(IBattlePokemon pokemon)
{
var call = pokemon.ReceivedCalls().FirstOrDefault(c => c.GetMethodInfo().Name == "SetStatus");
return call != null ? ((StringKey)call.GetArguments()[0]!).ToString() : null;

View File

@@ -21,9 +21,9 @@ public class BatonPassTests
/// Creates a mocked Pokémon with a real volatile <see cref="ScriptSet"/> and a real
/// <see cref="StatBoostStatisticSet"/>.
/// </summary>
private static IPokemon CreateMockPokemon(out IScriptSet volatileSet)
private static IBattlePokemon CreateMockPokemon(out IScriptSet volatileSet)
{
var pokemon = Substitute.For<IPokemon>();
var pokemon = Substitute.For<IBattlePokemon>();
pokemon.GetScripts().Returns(_ => new ScriptIterator(Array.Empty<IEnumerable<ScriptContainer>>()));
var set = new ScriptSet(pokemon);
pokemon.Volatile.Returns(set);
@@ -36,8 +36,8 @@ public class BatonPassTests
/// Creates a fully mocked test setup for Baton Pass tests. The Pokémon to switch in is stored in the
/// move choice's <see cref="IMoveChoice.AdditionalData"/> under the <c>to_switch</c> key.
/// </summary>
private static (BatonPass script, IExecutingMove move, IPokemon user, IPokemon toSwitch, IBattleSide side,
IScriptSet userVolatile, IScriptSet switchInVolatile) CreateTestSetup()
private static (BatonPass script, IExecutingMove move, IBattlePokemon user, IBattlePokemon toSwitch, IBattleSide
side, IScriptSet userVolatile, IScriptSet switchInVolatile) CreateTestSetup()
{
var script = new BatonPass();
var move = Substitute.For<IExecutingMove>();
@@ -52,11 +52,9 @@ public class BatonPassTests
var battle = Substitute.For<IBattle>();
battle.Sides.Returns(new[] { side });
var battleData = Substitute.For<IPokemonBattleData>();
battleData.Battle.Returns(battle);
battleData.SideIndex.Returns((byte)0);
battleData.Position.Returns((byte)1);
user.BattleData.Returns(battleData);
user.Battle.Returns(battle);
user.SideIndex.Returns((byte)0);
user.Position.Returns((byte)1);
move.User.Returns(user);
return (script, move, user, toSwitch, side, userVolatile, switchInVolatile);
@@ -213,7 +211,7 @@ public class BatonPassTests
script.OnSecondaryEffect(move, user, 0);
// Assert
side.DidNotReceive().SwapPokemon(Arg.Any<byte>(), Arg.Any<IPokemon?>());
side.DidNotReceive().SwapPokemon(Arg.Any<byte>(), Arg.Any<IBattlePokemon?>());
}
/// <summary>
@@ -230,26 +228,6 @@ public class BatonPassTests
script.OnSecondaryEffect(move, user, 0);
// Assert
side.DidNotReceive().SwapPokemon(Arg.Any<byte>(), Arg.Any<IPokemon?>());
}
/// <summary>
/// Technical test: if the user has no <see cref="IPokemon.BattleData"/>, the script returns without
/// switching and without clearing the user's volatile scripts.
/// </summary>
[Test]
public async Task OnSecondaryEffect_NoBattleData_DoesNotSwitch()
{
// Arrange
var (script, move, user, _, side, userVolatile, _) = CreateTestSetup();
user.BattleData.Returns((IPokemonBattleData?)null);
userVolatile.Add(new AutotomizeEffect());
// Act
script.OnSecondaryEffect(move, user, 0);
// Assert
side.DidNotReceive().SwapPokemon(Arg.Any<byte>(), Arg.Any<IPokemon?>());
await Assert.That(userVolatile.Get<AutotomizeEffect>()).IsNotNull();
side.DidNotReceive().SwapPokemon(Arg.Any<byte>(), Arg.Any<IBattlePokemon?>());
}
}

View File

@@ -17,28 +17,19 @@ public class BeakBlastTests
/// <summary>
/// Creates a fully mocked setup for driving <see cref="BeakBlast.OnBeforeTurnStart"/>.
/// </summary>
private static (BeakBlast script, ITurnChoice choice, IPokemon user, IScriptSet volatileSet, EventHook eventHook)
CreateChargeSetup(bool hasBattleData = true)
private static (BeakBlast script, ITurnChoice choice, IBattlePokemon user, IScriptSet volatileSet, EventHook
eventHook) CreateChargeSetup()
{
var script = new BeakBlast();
var user = Substitute.For<IPokemon>();
var user = Substitute.For<IBattlePokemon>();
user.GetScripts().Returns(_ => new ScriptIterator(Array.Empty<IEnumerable<ScriptContainer>>()));
IScriptSet volatileSet = new ScriptSet(user);
user.Volatile.Returns(volatileSet);
var eventHook = new EventHook();
if (hasBattleData)
{
var battle = Substitute.For<IBattle>();
battle.EventHook.Returns(eventHook);
var battleData = Substitute.For<IPokemonBattleData>();
battleData.Battle.Returns(battle);
user.BattleData.Returns(battleData);
}
else
{
user.BattleData.Returns((IPokemonBattleData?)null);
}
var battle = Substitute.For<IBattle>();
battle.EventHook.Returns(eventHook);
user.Battle.Returns(battle);
var choice = Substitute.For<ITurnChoice>();
choice.User.Returns(user);
@@ -85,24 +76,7 @@ public class BeakBlastTests
// Assert
await Assert.That(capturedEvent).IsNotNull();
await Assert.That(capturedEvent!.Message).IsEqualTo("beak_blast_charge");
await Assert.That((IPokemon)capturedEvent.Parameters!["user"]).IsEqualTo(user);
}
/// <summary>
/// Technical test: a Pokémon without <see cref="IPokemon.BattleData"/> is not in battle, so no charging
/// phase starts.
/// </summary>
[Test]
public async Task OnBeforeTurnStart_NoBattleData_DoesNotAddChargeEffect()
{
// Arrange
var (script, choice, _, volatileSet, _) = CreateChargeSetup(false);
// Act
script.OnBeforeTurnStart(choice);
// Assert
await Assert.That(volatileSet.Get<BeakBlastEffect>()).IsNull();
await Assert.That((IBattlePokemon)capturedEvent.Parameters!["user"]).IsEqualTo(user);
}
/// <summary>
@@ -115,8 +89,8 @@ public class BeakBlastTests
// Arrange
var script = new BeakBlast();
var move = Substitute.For<IExecutingMove>();
var target = Substitute.For<IPokemon>();
var user = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
var user = Substitute.For<IBattlePokemon>();
user.GetScripts().Returns(_ => new ScriptIterator(Array.Empty<IEnumerable<ScriptContainer>>()));
IScriptSet volatileSet = new ScriptSet(user);
user.Volatile.Returns(volatileSet);
@@ -133,26 +107,26 @@ public class BeakBlastTests
/// <summary>
/// Creates a fully mocked setup for driving <see cref="BeakBlastEffect.OnIncomingHit"/>.
/// </summary>
private static (BeakBlastEffect effect, IExecutingMove move, IPokemon target, IPokemon attacker)
private static (BeakBlastEffect effect, IExecutingMove move, IBattlePokemon target, IBattlePokemon attacker)
CreateIncomingHitSetup(bool isContact)
{
var effect = new BeakBlastEffect();
var move = Substitute.For<IExecutingMove>();
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
var hitData = Substitute.For<IHitData>();
hitData.IsContact.Returns(isContact);
move.GetHitData(target, 0).Returns(hitData);
var attacker = Substitute.For<IPokemon>();
var attacker = Substitute.For<IBattlePokemon>();
move.User.Returns(attacker);
return (effect, move, target, attacker);
}
/// <summary>
/// Helper to extract the status name from a Pokémon's received <see cref="IPokemon.SetStatus"/> calls.
/// Helper to extract the status name from a Pokémon's received <see cref="IBattlePokemon.SetStatus"/> calls.
/// </summary>
private static string? GetStatusSet(IPokemon pokemon)
private static string? GetStatusSet(IBattlePokemon pokemon)
{
var call = pokemon.ReceivedCalls().FirstOrDefault(c => c.GetMethodInfo().Name == "SetStatus");
return call != null ? ((StringKey)call.GetArguments()[0]!).ToString() : null;

View File

@@ -19,11 +19,11 @@ public class BeatUpTests
{
/// <summary>
/// Creates a mocked party member with the given base Attack stat, usability, and optional non-volatile
/// status script in its <see cref="IPokemon.StatusScript"/>.
/// status script in its <see cref="IBattlePokemon.StatusScript"/>.
/// </summary>
private static IPokemon CreatePartyMember(ushort baseAttack = 100, bool usable = true, Script? status = null)
private static IBattlePokemon CreatePartyMember(ushort baseAttack = 100, bool usable = true, Script? status = null)
{
var pokemon = Substitute.For<IPokemon>();
var pokemon = Substitute.For<IBattlePokemon>();
pokemon.IsUsable.Returns(usable);
pokemon.StatusScript.Returns(status == null ? new ScriptContainer() : new ScriptContainer(status));
var form = Substitute.For<IForm>();
@@ -35,24 +35,20 @@ public class BeatUpTests
/// <summary>
/// Creates a fully mocked test setup where the user and the given other Pokémon form a party in a battle.
/// </summary>
private static (BeatUp script, IMoveChoice choice, IExecutingMove move) CreateTestSetup(IPokemon user,
params IPokemon?[] otherPartyMembers)
private static (BeatUp script, IMoveChoice choice, IExecutingMove move) CreateTestSetup(IBattlePokemon user,
params IBattlePokemon?[] otherPartyMembers)
{
var script = new BeatUp();
var members = new List<IPokemon?> { user };
var members = new List<IBattlePokemon?> { user };
members.AddRange(otherPartyMembers);
var party = Substitute.For<IPokemonParty>();
party.GetEnumerator().Returns(_ => members.GetEnumerator());
var battleParty = Substitute.For<IBattleParty>();
battleParty.Party.Returns(party);
battleParty.BattlePokemon.Returns(members);
var battle = Substitute.For<IBattle>();
battle.Parties.Returns(new[] { battleParty });
var battleData = Substitute.For<IPokemonBattleData>();
battleData.Battle.Returns(battle);
user.BattleData.Returns(battleData);
user.Battle.Returns(battle);
var choice = Substitute.For<IMoveChoice>();
choice.User.Returns(user);
@@ -121,28 +117,6 @@ public class BeatUpTests
await Assert.That(numberOfHits).IsEqualTo((byte)1);
}
/// <summary>
/// Technical test: outside of battle (no <see cref="IPokemon.BattleData"/>) there are no relevant party
/// members, and the number of hits falls back to a single strike.
/// </summary>
[Test]
public async Task ChangeNumberOfHits_NoBattleData_SingleHit()
{
// Arrange
var script = new BeatUp();
var user = Substitute.For<IPokemon>();
user.BattleData.Returns((IPokemonBattleData?)null);
var choice = Substitute.For<IMoveChoice>();
choice.User.Returns(user);
byte numberOfHits = 3;
// Act
script.ChangeNumberOfHits(choice, ref numberOfHits);
// Assert
await Assert.That(numberOfHits).IsEqualTo((byte)1);
}
/// <summary>
/// Bulbapedia: "the base power per strike is no longer 10, but instead individually based on the Attack
/// base stats of the party Pokémon: BasePower = BaseAttack(PartyMember)/10 + 5".
@@ -155,7 +129,7 @@ public class BeatUpTests
// Arrange
var user = CreatePartyMember(baseAttack);
var (script, _, move) = CreateTestSetup(user);
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
ushort basePower = 10;
// Act
@@ -175,7 +149,7 @@ public class BeatUpTests
// Arrange
var user = CreatePartyMember(100);
var (script, _, move) = CreateTestSetup(user, CreatePartyMember(250));
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
ushort basePower = 10;
// Act
@@ -197,7 +171,7 @@ public class BeatUpTests
var user = CreatePartyMember(100);
var (script, _, move) = CreateTestSetup(user, CreatePartyMember(250, status: new Burned()),
CreatePartyMember(60));
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
ushort basePower = 10;
// Act
@@ -217,7 +191,7 @@ public class BeatUpTests
// Arrange
var user = CreatePartyMember(100);
var (script, _, move) = CreateTestSetup(user);
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
ushort basePower = 10;
// Act

View File

@@ -13,13 +13,13 @@ namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves;
public class BelchTests
{
/// <summary>
/// Creates a fully mocked test setup where the user's <see cref="IPokemonBattleData.ConsumedItems"/>
/// Creates a fully mocked test setup where the user's <see cref="IBattlePokemon.ConsumedItems"/>
/// contains one item per given <see cref="ItemCategory"/>.
/// </summary>
private static (Belch script, IMoveChoice choice) CreateTestSetup(params ItemCategory[] consumedItemCategories)
{
var script = new Belch();
var user = Substitute.For<IPokemon>();
var user = Substitute.For<IBattlePokemon>();
var items = consumedItemCategories.Select(category =>
{
@@ -27,9 +27,7 @@ public class BelchTests
item.Category.Returns(category);
return item;
}).ToArray();
var battleData = Substitute.For<IPokemonBattleData>();
battleData.ConsumedItems.Returns(items);
user.BattleData.Returns(battleData);
user.ConsumedItems.Returns(items);
var choice = Substitute.For<IMoveChoice>();
choice.User.Returns(user);
@@ -108,26 +106,4 @@ public class BelchTests
// Assert
await Assert.That(prevent).IsFalse();
}
/// <summary>
/// Technical test: outside of battle (no <see cref="IPokemon.BattleData"/>) the script does not prevent
/// selection.
/// </summary>
[Test]
public async Task PreventMoveSelection_NoBattleData_SelectionAllowed()
{
// Arrange
var script = new Belch();
var user = Substitute.For<IPokemon>();
user.BattleData.Returns((IPokemonBattleData?)null);
var choice = Substitute.For<IMoveChoice>();
choice.User.Returns(user);
var prevent = false;
// Act
script.PreventMoveSelection(choice, ref prevent);
// Assert
await Assert.That(prevent).IsFalse();
}
}

View File

@@ -14,11 +14,11 @@ public class BellyDrumTests
/// <summary>
/// Creates a fully mocked test setup for Belly Drum. The target of the secondary effect is the user itself.
/// </summary>
private static (BellyDrum script, IExecutingMove move, IPokemon user, IHitData hitData) CreateTestSetup(uint maxHp,
uint currentHp, sbyte attackBoost = 0)
private static (BellyDrum script, IExecutingMove move, IBattlePokemon user, IHitData hitData) CreateTestSetup(
uint maxHp, uint currentHp, sbyte attackBoost = 0)
{
var script = new BellyDrum();
var user = Substitute.For<IPokemon>();
var user = Substitute.For<IBattlePokemon>();
user.BoostedStats.Returns(new StatisticSet<uint>(maxHp, 10, 10, 10, 10, 10));
user.CurrentHealth.Returns(currentHp);
user.StatBoost.Returns(new StatBoostStatisticSet(0, attackBoost, 0, 0, 0, 0));
@@ -34,7 +34,7 @@ public class BellyDrumTests
/// <summary>
/// Helper to extract the received Damage call from the user, if any.
/// </summary>
private static (uint damage, DamageSource source, bool forceDamage)? GetDamageCall(IPokemon user)
private static (uint damage, DamageSource source, bool forceDamage)? GetDamageCall(IBattlePokemon user)
{
var call = user.ReceivedCalls().FirstOrDefault(c => c.GetMethodInfo().Name == "Damage");
if (call == null)
@@ -46,7 +46,7 @@ public class BellyDrumTests
/// <summary>
/// Helper to extract the received ChangeStatBoost call from the user, if any.
/// </summary>
private static (Statistic stat, sbyte change, bool selfInflicted)? GetStatBoostCall(IPokemon user)
private static (Statistic stat, sbyte change, bool selfInflicted)? GetStatBoostCall(IBattlePokemon user)
{
var call = user.ReceivedCalls().FirstOrDefault(c => c.GetMethodInfo().Name == "ChangeStatBoost");
if (call == null)

View File

@@ -12,17 +12,17 @@ namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves;
/// </summary>
public class BestowTests
{
private static (Bestow script, IExecutingMove move, IPokemon user, IPokemon target, IHitData hitData)
private static (Bestow script, IExecutingMove move, IBattlePokemon user, IBattlePokemon target, IHitData hitData)
CreateTestSetup(IItem? userItem, IItem? targetItem)
{
var script = new Bestow();
var move = Substitute.For<IExecutingMove>();
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
var hitData = Substitute.For<IHitData>();
move.GetHitData(target, 0).Returns(hitData);
target.HeldItem.Returns(targetItem);
var user = Substitute.For<IPokemon>();
var user = Substitute.For<IBattlePokemon>();
user.HeldItem.Returns(userItem);
user.RemoveHeldItemForBattle().Returns(userItem);
move.User.Returns(user);
@@ -68,7 +68,7 @@ public class BestowTests
/// <summary>
/// Bulbapedia: "Items given away in Trainer battles return to the original Pokémon after the battle."
/// The item is taken from the user through <see cref="IPokemon.RemoveHeldItemForBattle"/>, which only
/// The item is taken from the user through <see cref="IBattlePokemon.RemoveHeldItemForBattle"/>, which only
/// removes the item for the duration of the battle.
/// </summary>
[Test]

View File

@@ -13,16 +13,16 @@ namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves;
/// </summary>
public class BideTests
{
private static (Bide script, IExecutingMove move, IPokemon user, IPokemon target, IScriptSet userVolatile, IHitData
hitData) CreateTestSetup()
private static (Bide script, IExecutingMove move, IBattlePokemon user, IBattlePokemon target, IScriptSet
userVolatile, IHitData hitData) CreateTestSetup()
{
var script = new Bide();
var move = Substitute.For<IExecutingMove>();
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
var hitData = Substitute.For<IHitData>();
move.GetHitData(target, 0).Returns(hitData);
var user = Substitute.For<IPokemon>();
var user = Substitute.For<IBattlePokemon>();
user.GetScripts().Returns(_ => new ScriptIterator(new List<IEnumerable<ScriptContainer>>()));
// A real script set so the volatile Bide effect can actually be added, retrieved and removed.
var userVolatile = new ScriptSet(user);
@@ -32,12 +32,10 @@ public class BideTests
return (script, move, user, target, userVolatile, hitData);
}
private static IPokemon CreateAttacker(bool onBattlefield = true)
private static IBattlePokemon CreateAttacker(bool onBattlefield = true)
{
var attacker = Substitute.For<IPokemon>();
var battleData = Substitute.For<IPokemonBattleData>();
battleData.IsOnBattlefield.Returns(onBattlefield);
attacker.BattleData.Returns(battleData);
var attacker = Substitute.For<IBattlePokemon>();
attacker.IsOnBattlefield.Returns(onBattlefield);
return attacker;
}
@@ -45,8 +43,8 @@ public class BideTests
/// Adds a <see cref="BideEffect"/> to the user's volatile scripts, as if Bide has already been storing
/// energy for the given number of executed turns.
/// </summary>
private static BideEffect AddStoredBideEffect(IScriptSet userVolatile, IPokemon user, byte turns, uint damageTaken,
params IPokemon[] hitBy)
private static BideEffect AddStoredBideEffect(IScriptSet userVolatile, IBattlePokemon user, byte turns,
uint damageTaken, params IBattlePokemon[] hitBy)
{
var effect = new BideEffect(user)
{
@@ -61,13 +59,13 @@ public class BideTests
/// <summary>
/// Helper to check whether a Pokémon received any Damage call.
/// </summary>
private static bool ReceivedDamage(IPokemon pokemon) =>
private static bool ReceivedDamage(IBattlePokemon pokemon) =>
pokemon.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "Damage");
/// <summary>
/// Helper to extract the damage amount from a Pokémon's received Damage calls.
/// </summary>
private static uint? GetDamageAmount(IPokemon pokemon)
private static uint? GetDamageAmount(IBattlePokemon pokemon)
{
var call = pokemon.ReceivedCalls().FirstOrDefault(c => c.GetMethodInfo().Name == "Damage");
return call != null ? (uint)call.GetArguments()[0]! : null;
@@ -76,7 +74,7 @@ public class BideTests
/// <summary>
/// Helper to extract the damage source from a Pokémon's received Damage calls.
/// </summary>
private static DamageSource? GetDamageSource(IPokemon pokemon)
private static DamageSource? GetDamageSource(IBattlePokemon pokemon)
{
var call = pokemon.ReceivedCalls().FirstOrDefault(c => c.GetMethodInfo().Name == "Damage");
return call != null ? (DamageSource)call.GetArguments()[1]! : null;
@@ -245,7 +243,7 @@ public class BideTests
public async Task BideEffect_OnDamage_AccumulatesDamageTaken()
{
// Arrange
var user = Substitute.For<IPokemon>();
var user = Substitute.For<IBattlePokemon>();
var effect = new BideEffect(user);
// Act - the user drops from 100 to 60 HP, then from 60 to 50 HP
@@ -265,10 +263,10 @@ public class BideTests
public async Task BideEffect_OnIncomingHit_RecordsAttacker()
{
// Arrange
var user = Substitute.For<IPokemon>();
var user = Substitute.For<IBattlePokemon>();
var effect = new BideEffect(user);
var incomingMove = Substitute.For<IExecutingMove>();
var attacker = Substitute.For<IPokemon>();
var attacker = Substitute.For<IBattlePokemon>();
incomingMove.User.Returns(attacker);
// Act

View File

@@ -44,16 +44,16 @@ public class BindTests
}
}
private static (Bind script, IExecutingMove move, IPokemon user, IPokemon target, IScriptSet targetVolatile)
CreateTestSetup(params Script[] userScripts)
private static (Bind script, IExecutingMove move, IBattlePokemon user, IBattlePokemon target, IScriptSet
targetVolatile) CreateTestSetup(params Script[] userScripts)
{
var script = new Bind();
var move = Substitute.For<IExecutingMove>();
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
var targetVolatile = Substitute.For<IScriptSet>();
target.Volatile.Returns(targetVolatile);
var user = Substitute.For<IPokemon>();
var user = Substitute.For<IBattlePokemon>();
// RunScriptHook iterates the user's scripts; give the mock a real iterator so the ModifyBind trigger
// pass runs (empty unless the test attaches scripts such as the Grip Claw / Binding Band stand-in).
var containers = userScripts.Select(IEnumerable<ScriptContainer> (s) => new ScriptContainer(s)).ToArray();
@@ -73,7 +73,7 @@ public class BindTests
/// <summary>
/// Helper to extract the damage amount from the target's first received Damage call.
/// </summary>
private static uint? GetDamageAmount(IPokemon pokemon)
private static uint? GetDamageAmount(IBattlePokemon pokemon)
{
var call = pokemon.ReceivedCalls().FirstOrDefault(c => c.GetMethodInfo().Name == "Damage");
return call != null ? (uint)call.GetArguments()[0]! : null;
@@ -82,7 +82,7 @@ public class BindTests
/// <summary>
/// Runs end-of-turn handling on the effect repeatedly and counts how many turns dealt damage to the target.
/// </summary>
private static int CountEndTurnDamageTicks(BindEffect effect, IPokemon target, int maxTurns = 10)
private static int CountEndTurnDamageTicks(BindEffect effect, IBattlePokemon target, int maxTurns = 10)
{
var battle = Substitute.For<IBattle>();
for (var i = 0; i < maxTurns; i++)
@@ -204,7 +204,7 @@ public class BindTests
public async Task BindEffect_WhileActive_PreventsSwitching()
{
// Arrange
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
var effect = new BindEffect(target, 5, 1f / 8f);
var prevent = false;
@@ -223,7 +223,7 @@ public class BindTests
public async Task BindEffect_WhileActive_PreventsRunningAway()
{
// Arrange
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
var effect = new BindEffect(target, 5, 1f / 8f);
var prevent = false;
@@ -242,7 +242,7 @@ public class BindTests
public async Task BindEffect_AfterDurationExpires_NoLongerPreventsSwitching()
{
// Arrange
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
target.MaxHealth.Returns(160u);
var effect = new BindEffect(target, 1, 1f / 8f);

View File

@@ -14,14 +14,15 @@ namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves;
/// </summary>
public class BlockTests
{
private static (Block script, IExecutingMove move, IPokemon target, ScriptSet targetVolatile) CreateTestSetup()
private static (Block script, IExecutingMove move, IBattlePokemon target, ScriptSet targetVolatile)
CreateTestSetup()
{
var script = new Block();
var move = Substitute.For<IExecutingMove>();
var user = Substitute.For<IPokemon>();
var user = Substitute.For<IBattlePokemon>();
move.User.Returns(user);
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
// Use a real script set so the volatile script added by Block can be inspected afterwards.
var targetVolatile = new ScriptSet(target);
target.Volatile.Returns(targetVolatile);
@@ -105,10 +106,8 @@ public class BlockTests
var battle = Substitute.For<IBattle>();
battle.Library.Returns(library);
var battleData = Substitute.For<IPokemonBattleData>();
battleData.Battle.Returns(battle);
move.User.BattleData.Returns(battleData);
target.BattleData.Returns(battleData);
move.User.Battle.Returns(battle);
target.Battle.Returns(battle);
// Act
script.OnSecondaryEffect(move, target, 0);

View File

@@ -20,12 +20,12 @@ namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves;
/// </summary>
public class BounceTests
{
private static (Bounce script, IExecutingMove move, IPokemon user, ScriptSet userVolatile, IMoveChoice moveChoice,
IBattleRandom random) CreateTestSetup()
private static (Bounce script, IExecutingMove move, IBattlePokemon user, ScriptSet userVolatile, IMoveChoice
moveChoice, IBattleRandom random) CreateTestSetup()
{
var script = new Bounce();
var move = Substitute.For<IExecutingMove>();
var user = Substitute.For<IPokemon>();
var user = Substitute.For<IBattlePokemon>();
// Use a real script set so the charge volatile added by Bounce can be inspected afterwards.
var userVolatile = new ScriptSet(user);
user.Volatile.Returns(userVolatile);
@@ -39,9 +39,7 @@ public class BounceTests
var random = Substitute.For<IBattleRandom>();
battle.Random.Returns(random);
battle.EventHook.Returns(new EventHook());
var battleData = Substitute.For<IPokemonBattleData>();
battleData.Battle.Returns(battle);
user.BattleData.Returns(battleData);
user.Battle.Returns(battle);
return (script, move, user, userVolatile, moveChoice, random);
}
@@ -148,7 +146,7 @@ public class BounceTests
{
// Arrange
var (script, move, user, _, _, random) = CreateTestSetup();
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
random.EffectChance(30, move, target, 0).Returns(true);
// Act
@@ -168,7 +166,7 @@ public class BounceTests
{
// Arrange
var (script, move, _, _, _, random) = CreateTestSetup();
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
random.EffectChance(30, move, target, 0).Returns(false);
// Act
@@ -187,7 +185,7 @@ public class BounceTests
{
// Arrange
var (script, move, _, _, _, random) = CreateTestSetup();
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
// Act
script.OnSecondaryEffect(move, target, 0);
@@ -197,24 +195,6 @@ public class BounceTests
await Assert.That(random.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "EffectChance")).IsTrue();
}
/// <summary>
/// Technical test: outside of battle (no battle data) the secondary effect does nothing and does not throw.
/// </summary>
[Test]
public async Task OnSecondaryEffect_NoBattleData_DoesNotParalyzeTarget()
{
// Arrange
var (script, move, user, _, _, _) = CreateTestSetup();
user.BattleData.Returns((IPokemonBattleData?)null);
var target = Substitute.For<IPokemon>();
// Act
script.OnSecondaryEffect(move, target, 0);
// Assert
await Assert.That(target.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "SetStatus")).IsFalse();
}
/// <summary>
/// Bulbapedia: "While in Sky-High status, the user is invulnerable to most moves".
/// The <see cref="ChargeBounceEffect"/> added by the charge turn blocks incoming hits from moves that

View File

@@ -15,21 +15,19 @@ namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves;
public class BrickBreakTests
{
/// <summary>
/// Creates a mocked Pokémon whose <see cref="IPokemonBattleData.BattleSide"/> is the given side.
/// Creates a mocked Pokémon whose <see cref="IBattlePokemon.BattleSide"/> is the given side.
/// </summary>
private static IPokemon CreatePokemonOnSide(IBattleSide side)
private static IBattlePokemon CreatePokemonOnSide(IBattleSide side)
{
var pokemon = Substitute.For<IPokemon>();
var battleData = Substitute.For<IPokemonBattleData>();
battleData.BattleSide.Returns(side);
pokemon.BattleData.Returns(battleData);
var pokemon = Substitute.For<IBattlePokemon>();
pokemon.BattleSide.Returns(side);
return pokemon;
}
/// <summary>
/// Creates a fully mocked test setup where the user targets a Pokémon on the opposing side.
/// </summary>
private static (BrickBreak script, IExecutingMove move, IPokemon user, IScriptSet userSideScripts, IScriptSet
private static (BrickBreak script, IExecutingMove move, IBattlePokemon user, IScriptSet userSideScripts, IScriptSet
targetSideScripts) CreateTestSetup()
{
var script = new BrickBreak();
@@ -45,7 +43,7 @@ public class BrickBreakTests
var user = CreatePokemonOnSide(userSide);
move.User.Returns(user);
var target = CreatePokemonOnSide(targetSide);
move.Targets.Returns(new IPokemon?[] { target });
move.Targets.Returns(new IBattlePokemon?[] { target });
return (script, move, user, userSideScripts, targetSideScripts);
}
@@ -115,8 +113,8 @@ public class BrickBreakTests
{
// Arrange
var (script, move, user, userSideScripts, _) = CreateTestSetup();
var ally = CreatePokemonOnSide(user.BattleData!.BattleSide);
move.Targets.Returns(new IPokemon?[] { ally });
var ally = CreatePokemonOnSide(user.BattleSide);
move.Targets.Returns(new IBattlePokemon?[] { ally });
// Act
script.OnBeforeMove(move);
@@ -146,7 +144,7 @@ public class BrickBreakTests
var user = CreatePokemonOnSide(userSide);
move.User.Returns(user);
var target = CreatePokemonOnSide(targetSide);
move.Targets.Returns(new IPokemon?[] { target });
move.Targets.Returns(new IBattlePokemon?[] { target });
// Act
script.OnBeforeMove(move);
@@ -165,10 +163,9 @@ public class BrickBreakTests
// Arrange
var script = new BrickBreak();
var move = Substitute.For<IExecutingMove>();
var user = Substitute.For<IPokemon>();
user.BattleData.Returns((IPokemonBattleData?)null);
var user = Substitute.For<IBattlePokemon>();
move.User.Returns(user);
move.Targets.Returns(Array.Empty<IPokemon?>());
move.Targets.Returns(Array.Empty<IBattlePokemon?>());
// Act & Assert
await Assert.That(() => script.OnBeforeMove(move)).ThrowsNothing();

View File

@@ -12,13 +12,13 @@ public class BrineTests
{
/// <summary>
/// Creates a fully mocked test setup for Brine tests, with a target whose max HP
/// (<see cref="IPokemon.BoostedStats"/>) and <see cref="IPokemon.CurrentHealth"/> are configured.
/// (<see cref="IBattlePokemon.BoostedStats"/>) and <see cref="IBattlePokemon.CurrentHealth"/> are configured.
/// </summary>
private static (Brine brine, IExecutingMove move, IPokemon target) CreateTestSetup(uint maxHp, uint currentHp)
private static (Brine brine, IExecutingMove move, IBattlePokemon target) CreateTestSetup(uint maxHp, uint currentHp)
{
var brine = new Brine();
var move = Substitute.For<IExecutingMove>();
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
target.BoostedStats.Returns(new StatisticSet<uint>(maxHp, 0, 0, 0, 0, 0));
target.CurrentHealth.Returns(currentHp);
return (brine, move, target);

View File

@@ -41,8 +41,8 @@ public class BugBiteTests
/// real resolver with a single registered item script constructor for <see cref="BerryEffectName"/>, so
/// eating a Berry runs a <see cref="RecordingItemScript"/> that the test can inspect.
/// </summary>
private static (BugBite bugBite, IExecutingMove move, IPokemon target, IHitData hitData, List<RecordingItemScript>
createdItemScripts) CreateTestSetup(IItem? targetHeldItem, bool canSteal = true)
private static (BugBite bugBite, IExecutingMove move, IBattlePokemon target, IHitData hitData,
List<RecordingItemScript> createdItemScripts) CreateTestSetup(IItem? targetHeldItem, bool canSteal = true)
{
var bugBite = new BugBite();
@@ -66,17 +66,14 @@ public class BugBiteTests
dynamicLibrary.ScriptResolver.Returns(resolver);
battle.Library.Returns(dynamicLibrary);
var battleData = Substitute.For<IPokemonBattleData>();
battleData.Battle.Returns(battle);
var user = Substitute.For<IPokemon>();
user.BattleData.Returns(battleData);
var user = Substitute.For<IBattlePokemon>();
user.Battle.Returns(battle);
var move = Substitute.For<IExecutingMove>();
move.User.Returns(user);
move.Battle.Returns(battle);
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
target.HeldItem.Returns(targetHeldItem);
if (targetHeldItem != null && canSteal)
{
@@ -111,7 +108,7 @@ public class BugBiteTests
/// <summary>
/// Bulbapedia: "If the target is holding a Berry, the user will eat the Berry and gain its effect."
/// Eating the Berry removes it from the target via <see cref="IPokemon.ForceSetHeldItem"/>.
/// Eating the Berry removes it from the target via <see cref="IBattlePokemon.ForceSetHeldItem"/>.
/// </summary>
[Test]
public async Task OnSecondaryEffect_TargetHoldsBerry_BerryIsRemovedFromTarget()
@@ -202,7 +199,7 @@ public class BugBiteTests
/// <summary>
/// Bulbapedia: "Bug Bite will not consume the Berry of a target that has the Ability Sticky Hold."
/// When the Berry cannot be stolen (<see cref="IPokemon.TryStealHeldItem"/> returns false, as with
/// When the Berry cannot be stolen (<see cref="IBattlePokemon.TryStealHeldItem"/> returns false, as with
/// Sticky Hold), the target keeps its Berry and the effect fails.
/// </summary>
[Test]
@@ -219,25 +216,4 @@ public class BugBiteTests
await Assert.That(createdItemScripts.Count).IsEqualTo(0);
await Assert.That(hitData.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "Fail")).IsTrue();
}
/// <summary>
/// If the user has no <see cref="IPokemon.BattleData"/>, the script does nothing: no Berry is eaten
/// and the hit is not failed.
/// </summary>
[Test]
public async Task OnSecondaryEffect_UserHasNoBattleData_DoesNothing()
{
// Arrange
var (bugBite, move, target, hitData, _) = CreateTestSetup(CreateBerry());
var user = Substitute.For<IPokemon>();
user.BattleData.Returns((IPokemonBattleData?)null);
move.User.Returns(user);
// Act
bugBite.OnSecondaryEffect(move, target, 0);
// Assert
await Assert.That(target.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "ForceSetHeldItem")).IsFalse();
await Assert.That(hitData.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "Fail")).IsFalse();
}
}

View File

@@ -18,8 +18,8 @@ public class BurnUpTests
/// <see cref="TypeLibrary"/> with "fire" and "water" registered. The user is a Water-type, optionally
/// also carrying the Fire type.
/// </summary>
private static (BurnUp burnUp, IExecutingMove move, IPokemon user, IPokemon target, IHitData hitData, TypeIdentifier
fireType) CreateTestSetup(bool userIsFireType, bool userIsFrozen = false)
private static (BurnUp burnUp, IExecutingMove move, IBattlePokemon user, IBattlePokemon target, IHitData hitData,
TypeIdentifier fireType) CreateTestSetup(bool userIsFireType, bool userIsFrozen = false)
{
var burnUp = new BurnUp();
@@ -33,11 +33,9 @@ public class BurnUpTests
dynamicLibrary.StaticLibrary.Returns(staticLibrary);
var battle = Substitute.For<IBattle>();
battle.Library.Returns(dynamicLibrary);
var battleData = Substitute.For<IPokemonBattleData>();
battleData.Battle.Returns(battle);
var user = Substitute.For<IPokemon>();
user.BattleData.Returns(battleData);
var user = Substitute.For<IBattlePokemon>();
user.Battle.Returns(battle);
user.Types.Returns(userIsFireType
? new List<TypeIdentifier> { fireType, waterType }
: new List<TypeIdentifier> { waterType });
@@ -46,7 +44,7 @@ public class BurnUpTests
var move = Substitute.For<IExecutingMove>();
move.User.Returns(user);
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
var hitData = Substitute.For<IHitData>();
move.GetHitData(target, 0).Returns(hitData);
@@ -54,9 +52,9 @@ public class BurnUpTests
}
/// <summary>
/// Helper that checks whether <see cref="IPokemon.RemoveType"/> was called with the given type.
/// Helper that checks whether <see cref="IBattlePokemon.RemoveType"/> was called with the given type.
/// </summary>
private static bool ReceivedRemoveType(IPokemon user, TypeIdentifier type) =>
private static bool ReceivedRemoveType(IBattlePokemon user, TypeIdentifier type) =>
user.ReceivedCalls().Any(c =>
c.GetMethodInfo().Name == "RemoveType" && type.Equals((TypeIdentifier)c.GetArguments()[0]!));
@@ -177,23 +175,4 @@ public class BurnUpTests
// Assert
await Assert.That(user.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "ClearStatus")).IsFalse();
}
/// <summary>
/// If the user has no <see cref="IPokemon.BattleData"/>, the script does nothing: no type is removed
/// and the hit is not failed.
/// </summary>
[Test]
public async Task OnSecondaryEffect_UserHasNoBattleData_DoesNothing()
{
// Arrange
var (burnUp, move, user, target, hitData, _) = CreateTestSetup(true);
user.BattleData.Returns((IPokemonBattleData?)null);
// Act
burnUp.OnSecondaryEffect(move, target, 0);
// Assert
await Assert.That(user.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "RemoveType")).IsFalse();
await Assert.That(hitData.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "Fail")).IsFalse();
}
}

View File

@@ -13,7 +13,7 @@ namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves;
/// </summary>
public class CamouflageTests
{
private static (Camouflage script, IExecutingMove move, IPokemon user, IBattle battle) CreateTestSetup(
private static (Camouflage script, IExecutingMove move, IBattlePokemon user, IBattle battle) CreateTestSetup(
string? terrainName, string environmentName)
{
var script = new Camouflage();
@@ -24,7 +24,7 @@ public class CamouflageTests
battle.TerrainName.Returns(terrainName == null ? null : new StringKey?(new StringKey(terrainName)));
battle.EnvironmentName.Returns(new StringKey(environmentName));
var user = Substitute.For<IPokemon>();
var user = Substitute.For<IBattlePokemon>();
var move = Substitute.For<IExecutingMove>();
move.User.Returns(user);
move.Battle.Returns(battle);
@@ -34,9 +34,9 @@ public class CamouflageTests
/// <summary>
/// Helper that returns the single type the user was changed to, or null when
/// <see cref="IPokemon.SetTypes"/> was never called.
/// <see cref="IBattlePokemon.SetTypes"/> was never called.
/// </summary>
private static TypeIdentifier? GetSetType(IPokemon user)
private static TypeIdentifier? GetSetType(IBattlePokemon user)
{
var call = user.ReceivedCalls().FirstOrDefault(c => c.GetMethodInfo().Name == "SetTypes");
return call != null ? ((IReadOnlyList<TypeIdentifier>)call.GetArguments()[0]!).Single() : null;
@@ -56,7 +56,7 @@ public class CamouflageTests
battle.Library.StaticLibrary.Types.TryGetTypeIdentifier(expectedType, out var expected);
// Act
script.OnSecondaryEffect(move, Substitute.For<IPokemon>(), 0);
script.OnSecondaryEffect(move, Substitute.For<IBattlePokemon>(), 0);
// Assert
await Assert.That(GetSetType(user)!.Value).IsEqualTo(expected);
@@ -75,7 +75,7 @@ public class CamouflageTests
battle.Library.StaticLibrary.Types.TryGetTypeIdentifier(expectedType, out var expected);
// Act
script.OnSecondaryEffect(move, Substitute.For<IPokemon>(), 0);
script.OnSecondaryEffect(move, Substitute.For<IBattlePokemon>(), 0);
// Assert
await Assert.That(GetSetType(user)!.Value).IsEqualTo(expected);
@@ -92,7 +92,7 @@ public class CamouflageTests
battle.Library.StaticLibrary.Types.TryGetTypeIdentifier("normal", out var expected);
// Act
script.OnSecondaryEffect(move, Substitute.For<IPokemon>(), 0);
script.OnSecondaryEffect(move, Substitute.For<IBattlePokemon>(), 0);
// Assert
await Assert.That(GetSetType(user)!.Value).IsEqualTo(expected);
@@ -107,7 +107,7 @@ public class CamouflageTests
{
// Arrange
var (script, move, _, _) = CreateTestSetup(null, "field");
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
// Act
script.OnSecondaryEffect(move, target, 0);

View File

@@ -14,16 +14,16 @@ namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves;
/// </summary>
public class CaptivateTests
{
private static (Captivate script, IExecutingMove move, IPokemon user, IPokemon target, IHitData hitData)
private static (Captivate script, IExecutingMove move, IBattlePokemon user, IBattlePokemon target, IHitData hitData)
CreateTestSetup(Gender userGender, Gender targetGender)
{
var script = new Captivate();
var move = Substitute.For<IExecutingMove>();
var user = Substitute.For<IPokemon>();
var user = Substitute.For<IBattlePokemon>();
user.Gender.Returns(userGender);
move.User.Returns(user);
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
target.Gender.Returns(targetGender);
var hitData = Substitute.For<IHitData>();
move.GetHitData(target, 0).Returns(hitData);
@@ -34,7 +34,7 @@ public class CaptivateTests
/// <summary>
/// Helper that checks whether a stat boost change was applied to the given Pokémon.
/// </summary>
private static bool ReceivedStatBoost(IPokemon pokemon, Statistic stat, sbyte amount) =>
private static bool ReceivedStatBoost(IBattlePokemon pokemon, Statistic stat, sbyte amount) =>
pokemon.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "ChangeStatBoost" &&
(Statistic)c.GetArguments()[0]! == stat &&
(sbyte)c.GetArguments()[1]! == amount);

View File

@@ -25,7 +25,7 @@ public class ChangeTargetSpecialDefenseTests
/// <summary>
/// Helper to extract the arguments of the ChangeStatBoost call received by a substitute target.
/// </summary>
private static object?[]? GetStatBoostCallArgs(IPokemon target)
private static object?[]? GetStatBoostCallArgs(IBattlePokemon target)
{
var call = target.ReceivedCalls().FirstOrDefault(c => c.GetMethodInfo().Name == "ChangeStatBoost");
return call?.GetArguments();
@@ -41,9 +41,9 @@ public class ChangeTargetSpecialDefenseTests
// Arrange
var script = CreateInitializedScript();
var move = Substitute.For<IExecutingMove>();
var user = Substitute.For<IPokemon>();
var user = Substitute.For<IBattlePokemon>();
move.User.Returns(user);
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
// Act
script.OnSecondaryEffect(move, target, 0);
@@ -66,9 +66,9 @@ public class ChangeTargetSpecialDefenseTests
// Arrange
var script = CreateInitializedScript();
var move = Substitute.For<IExecutingMove>();
var user = Substitute.For<IPokemon>();
var user = Substitute.For<IBattlePokemon>();
move.User.Returns(user);
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
// Act
script.OnSecondaryEffect(move, target, 0);
@@ -90,9 +90,9 @@ public class ChangeTargetSpecialDefenseTests
// Arrange
var script = CreateInitializedScript();
var move = Substitute.For<IExecutingMove>();
var user = Substitute.For<IPokemon>();
var user = Substitute.For<IBattlePokemon>();
move.User.Returns(user);
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
// Act
script.OnSecondaryEffect(move, target, 0);
@@ -114,9 +114,9 @@ public class ChangeTargetSpecialDefenseTests
// Arrange
var script = CreateInitializedScript();
var move = Substitute.For<IExecutingMove>();
var user = Substitute.For<IPokemon>();
var user = Substitute.For<IBattlePokemon>();
move.User.Returns(user);
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
// Act
script.OnSecondaryEffect(move, target, 0);
@@ -137,7 +137,7 @@ public class ChangeTargetSpecialDefenseTests
// Arrange
var script = CreateInitializedScript();
var move = Substitute.For<IExecutingMove>();
var user = Substitute.For<IPokemon>();
var user = Substitute.For<IBattlePokemon>();
move.User.Returns(user);
// Act - the user is hit by its own move (e.g. redirected)
@@ -159,9 +159,9 @@ public class ChangeTargetSpecialDefenseTests
// Arrange
var script = CreateInitializedScript(amount);
var move = Substitute.For<IExecutingMove>();
var user = Substitute.For<IPokemon>();
var user = Substitute.For<IBattlePokemon>();
move.User.Returns(user);
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
// Act
script.OnSecondaryEffect(move, target, 0);

View File

@@ -15,11 +15,11 @@ namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves;
/// </summary>
public class ChargeTests
{
private static (Charge script, IExecutingMove move, IPokemon user, IScriptSet userVolatile) CreateTestSetup()
private static (Charge script, IExecutingMove move, IBattlePokemon user, IScriptSet userVolatile) CreateTestSetup()
{
var script = new Charge();
var move = Substitute.For<IExecutingMove>();
var user = Substitute.For<IPokemon>();
var user = Substitute.For<IBattlePokemon>();
// Use a real script set so the volatile script added by Charge can be inspected afterwards.
var userVolatile = new ScriptSet(user);
user.Volatile.Returns(userVolatile);
@@ -32,16 +32,14 @@ public class ChargeTests
/// Creates an executing move of the given type whose damage modifier can be changed by
/// <see cref="ChargeEffect.ChangeDamageModifier"/>.
/// </summary>
private static (IExecutingMove move, IPokemon target) CreateExecutingMoveOfType(string typeName)
private static (IExecutingMove move, IBattlePokemon target) CreateExecutingMoveOfType(string typeName)
{
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);
var target = Substitute.For<IBattlePokemon>();
target.Battle.Returns(battle);
library.StaticLibrary.Types.TryGetTypeIdentifier(typeName, out var typeIdentifier);
var useMove = Substitute.For<IMoveData>();
useMove.MoveType.Returns(typeIdentifier);
@@ -61,7 +59,7 @@ public class ChargeTests
var (script, move, user, _) = CreateTestSetup();
// Act
script.OnSecondaryEffect(move, Substitute.For<IPokemon>(), 0);
script.OnSecondaryEffect(move, Substitute.For<IBattlePokemon>(), 0);
// Assert
var boost = user.ReceivedCalls().SingleOrDefault(c => c.GetMethodInfo().Name == "ChangeStatBoost");
@@ -82,7 +80,7 @@ public class ChargeTests
var (script, move, _, userVolatile) = CreateTestSetup();
// Act
script.OnSecondaryEffect(move, Substitute.For<IPokemon>(), 0);
script.OnSecondaryEffect(move, Substitute.For<IBattlePokemon>(), 0);
// Assert
await Assert.That(userVolatile.Contains(ScriptUtils.ResolveName<ChargeEffect>())).IsTrue();
@@ -134,7 +132,7 @@ public class ChargeTests
{
// Arrange
var (script, move, user, userVolatile) = CreateTestSetup();
script.OnSecondaryEffect(move, Substitute.For<IPokemon>(), 0);
script.OnSecondaryEffect(move, Substitute.For<IBattlePokemon>(), 0);
await Assert.That(userVolatile.TryGet<ChargeEffect>(out var effect)).IsTrue();
var battle = Substitute.For<IBattle>();

View File

@@ -22,7 +22,8 @@ public class ChipAwayTests
var bypass = false;
// Act
script.BypassDefensiveStatBoosts(Substitute.For<IExecutingMove>(), Substitute.For<IPokemon>(), 0, ref bypass);
script.BypassDefensiveStatBoosts(Substitute.For<IExecutingMove>(), Substitute.For<IBattlePokemon>(), 0,
ref bypass);
// Assert
await Assert.That(bypass).IsTrue();
@@ -40,7 +41,8 @@ public class ChipAwayTests
var bypass = false;
// Act
script.BypassEvasionStatBoosts(Substitute.For<IExecutingMove>(), Substitute.For<IPokemon>(), 0, ref bypass);
script.BypassEvasionStatBoosts(Substitute.For<IExecutingMove>(), Substitute.For<IBattlePokemon>(), 0,
ref bypass);
// Assert
await Assert.That(bypass).IsTrue();

View File

@@ -19,8 +19,8 @@ public class Conversion2Tests
{
private static readonly IDynamicLibrary Library = LibraryHelpers.LoadLibrary();
private static (Conversion2 script, IExecutingMove move, IPokemon user, IPokemon target, IHitData hitData)
CreateTestSetup(TypeIdentifier? lastMoveType)
private static (Conversion2 script, IExecutingMove move, IBattlePokemon user, IBattlePokemon target, IHitData
hitData) CreateTestSetup(TypeIdentifier? lastMoveType)
{
var script = new Conversion2();
@@ -30,15 +30,13 @@ public class Conversion2Tests
battle.Library.Returns(Library);
battle.Random.Returns(random);
var userBattleData = Substitute.For<IPokemonBattleData>();
userBattleData.Battle.Returns(battle);
var user = Substitute.For<IPokemon>();
user.BattleData.Returns(userBattleData);
var user = Substitute.For<IBattlePokemon>();
user.Battle.Returns(battle);
var targetBattleData = Substitute.For<IPokemonBattleData>();
var target = Substitute.For<IBattlePokemon>();
if (lastMoveType == null)
{
targetBattleData.LastMoveChoice.Returns((IMoveChoice?)null);
target.LastMoveChoice.Returns((IMoveChoice?)null);
}
else
{
@@ -48,12 +46,9 @@ public class Conversion2Tests
learnedMove.MoveData.Returns(moveData);
var lastChoice = Substitute.For<IMoveChoice>();
lastChoice.ChosenMove.Returns(learnedMove);
targetBattleData.LastMoveChoice.Returns(lastChoice);
target.LastMoveChoice.Returns(lastChoice);
}
var target = Substitute.For<IPokemon>();
target.BattleData.Returns(targetBattleData);
var move = Substitute.For<IExecutingMove>();
move.User.Returns(user);
var hitData = Substitute.For<IHitData>();
@@ -70,9 +65,9 @@ public class Conversion2Tests
/// <summary>
/// Helper that returns the single type the user was changed to, or null when
/// <see cref="IPokemon.SetTypes"/> was never called.
/// <see cref="IBattlePokemon.SetTypes"/> was never called.
/// </summary>
private static TypeIdentifier? GetSetType(IPokemon user)
private static TypeIdentifier? GetSetType(IBattlePokemon user)
{
var call = user.ReceivedCalls().FirstOrDefault(c => c.GetMethodInfo().Name == "SetTypes");
return call != null ? ((IReadOnlyList<TypeIdentifier>)call.GetArguments()[0]!).Single() : null;

View File

@@ -22,15 +22,15 @@ public class ConversionTests
return learned;
}
private static (Conversion script, IExecutingMove move, IPokemon user, IPokemon target, IHitData hitData)
CreateTestSetup(params ILearnedMove?[] moves)
private static (Conversion script, IExecutingMove move, IBattlePokemon user, IBattlePokemon target, IHitData hitData
) CreateTestSetup(params ILearnedMove?[] moves)
{
var script = new Conversion();
var move = Substitute.For<IExecutingMove>();
var user = Substitute.For<IPokemon>();
var user = Substitute.For<IBattlePokemon>();
user.Moves.Returns(moves);
move.User.Returns(user);
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
var hitData = Substitute.For<IHitData>();
move.GetHitData(target, 0).Returns(hitData);
return (script, move, user, target, hitData);

View File

@@ -17,11 +17,10 @@ public class CopycatTests
private static (Copycat script, IMoveChoice choice) CreateTestSetup(string? lastMoveName)
{
var script = new Copycat();
var user = Substitute.For<IPokemon>();
var battleData = Substitute.For<IPokemonBattleData>();
var user = Substitute.For<IBattlePokemon>();
if (lastMoveName == null)
{
battleData.LastMoveChoice.Returns((IMoveChoice?)null);
user.LastMoveChoice.Returns((IMoveChoice?)null);
}
else
{
@@ -31,9 +30,8 @@ public class CopycatTests
learnedMove.MoveData.Returns(moveData);
var lastChoice = Substitute.For<IMoveChoice>();
lastChoice.ChosenMove.Returns(learnedMove);
battleData.LastMoveChoice.Returns(lastChoice);
user.LastMoveChoice.Returns(lastChoice);
}
user.BattleData.Returns(battleData);
var choice = Substitute.For<IMoveChoice>();
choice.User.Returns(user);
@@ -94,25 +92,4 @@ public class CopycatTests
choice.Received(1).Fail();
await Assert.That(moveName).IsEqualTo(new StringKey("copycat"));
}
/// <summary>
/// Technical test: outside of battle (no battle data) there is no last move, so Copycat fails.
/// </summary>
[Test]
public void ChangeMove_NoBattleData_Fails()
{
// Arrange
var script = new Copycat();
var user = Substitute.For<IPokemon>();
user.BattleData.Returns((IPokemonBattleData?)null);
var choice = Substitute.For<IMoveChoice>();
choice.User.Returns(user);
StringKey moveName = "copycat";
// Act
script.ChangeMove(choice, ref moveName);
// Assert
choice.Received(1).Fail();
}
}

View File

@@ -14,16 +14,14 @@ namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves;
/// </summary>
public class CoreEnforcerTests
{
private static (CoreEnforcer script, IExecutingMove move, IMoveChoice currentChoice, IPokemon target, IHitData
private static (CoreEnforcer script, IExecutingMove move, IMoveChoice currentChoice, IBattlePokemon target, IHitData
hitData, IBattle battle) CreateTestSetup()
{
var script = new CoreEnforcer();
var battle = Substitute.For<IBattle>();
var battleData = Substitute.For<IPokemonBattleData>();
battleData.Battle.Returns(battle);
var target = Substitute.For<IPokemon>();
target.BattleData.Returns(battleData);
var target = Substitute.For<IBattlePokemon>();
target.Battle.Returns(battle);
var currentChoice = Substitute.For<IMoveChoice>();
var move = Substitute.For<IExecutingMove>();
move.MoveChoice.Returns(currentChoice);
@@ -120,24 +118,6 @@ public class CoreEnforcerTests
hitData.Received(1).Fail();
}
/// <summary>
/// Technical test: a target without battle data (not in battle) is left untouched.
/// </summary>
[Test]
public void OnSecondaryEffect_TargetHasNoBattleData_DoesNothing()
{
// Arrange
var (script, move, _, target, hitData, _) = CreateTestSetup();
target.BattleData.Returns((IPokemonBattleData?)null);
// Act
script.OnSecondaryEffect(move, target, 0);
// Assert
target.DidNotReceive().SuppressAbility();
hitData.DidNotReceive().Fail();
}
/// <summary>
/// Bulbapedia: the condition is that "the target has already used a move" — an action by a different
/// Pokémon (e.g. the user's ally in a Double Battle) does not count as the target having acted.
@@ -148,7 +128,7 @@ public class CoreEnforcerTests
// Arrange
var (script, move, currentChoice, target, hitData, battle) = CreateTestSetup();
var allyChoice = Substitute.For<IMoveChoice>();
allyChoice.User.Returns(Substitute.For<IPokemon>());
allyChoice.User.Returns(Substitute.For<IBattlePokemon>());
SetTurnChoices(battle, allyChoice, currentChoice);
// Act
@@ -162,7 +142,7 @@ public class CoreEnforcerTests
/// <summary>
/// Bulbapedia: "The move cannot suppress certain signature abilities including Multitype, Stance
/// Change, Schooling, Comatose, Shields Down, Disguise, RKS System, Battle Bond, Power Construct".
/// The script requests the suppression unconditionally; <see cref="IPokemon.SuppressAbility"/> refuses
/// The script requests the suppression unconditionally; <see cref="IBattlePokemon.SuppressAbility"/> refuses
/// it when <see cref="IAbility.CanBeChanged"/> is false, so these abilities must carry that flag in the
/// Gen7 data.
/// </summary>

View File

@@ -21,9 +21,9 @@ public class CounterTests
/// Creates a user whose volatile scripts contain a <see cref="CounterHelperEffect"/> that has recorded
/// an incoming physical hit of the given damage by <paramref name="attacker"/>.
/// </summary>
private static IPokemon CreateUserHitBy(IPokemon? attacker, uint damage, bool physical = true)
private static IBattlePokemon CreateUserHitBy(IBattlePokemon? attacker, uint damage, bool physical = true)
{
var user = Substitute.For<IPokemon>();
var user = Substitute.For<IBattlePokemon>();
var userVolatile = new ScriptSet(user);
user.Volatile.Returns(userVolatile);
user.GetScripts().Returns(_ => new ScriptIterator(new List<IEnumerable<ScriptContainer>>()));
@@ -55,7 +55,7 @@ public class CounterTests
{
// Arrange
var script = new Counter();
var user = Substitute.For<IPokemon>();
var user = Substitute.For<IBattlePokemon>();
var userVolatile = new ScriptSet(user);
user.Volatile.Returns(userVolatile);
user.GetScripts().Returns(_ => new ScriptIterator(new List<IEnumerable<ScriptContainer>>()));
@@ -78,11 +78,11 @@ public class CounterTests
{
// Arrange
var script = new Counter();
var attacker = Substitute.For<IPokemon>();
var attacker = Substitute.For<IBattlePokemon>();
var user = CreateUserHitBy(attacker, 40);
var choice = Substitute.For<IMoveChoice>();
choice.User.Returns(user);
IReadOnlyList<IPokemon?> targets = new IPokemon?[] { Substitute.For<IPokemon>() };
IReadOnlyList<IBattlePokemon?> targets = new IBattlePokemon?[] { Substitute.For<IBattlePokemon>() };
// Act
script.ChangeTargets(choice, ref targets);
@@ -103,7 +103,7 @@ public class CounterTests
var user = CreateUserHitBy(null, 0);
var choice = Substitute.For<IMoveChoice>();
choice.User.Returns(user);
IReadOnlyList<IPokemon?> targets = new IPokemon?[] { Substitute.For<IPokemon>() };
IReadOnlyList<IBattlePokemon?> targets = new IBattlePokemon?[] { Substitute.For<IBattlePokemon>() };
// Act
script.ChangeTargets(choice, ref targets);
@@ -122,7 +122,7 @@ public class CounterTests
{
// Arrange
var script = new Counter();
var attacker = Substitute.For<IPokemon>();
var attacker = Substitute.For<IBattlePokemon>();
var user = CreateUserHitBy(attacker, damageTaken);
var move = Substitute.For<IExecutingMove>();
move.User.Returns(user);
@@ -144,9 +144,9 @@ public class CounterTests
{
// Arrange
var script = new Counter();
var attacker = Substitute.For<IPokemon>();
var attacker = Substitute.For<IBattlePokemon>();
var user = CreateUserHitBy(attacker, 40);
var someoneElse = Substitute.For<IPokemon>();
var someoneElse = Substitute.For<IBattlePokemon>();
var move = Substitute.For<IExecutingMove>();
move.User.Returns(user);
var hitData = Substitute.For<IHitData>();
@@ -167,7 +167,7 @@ public class CounterTests
public async Task CounterHelperEffect_PhysicalHit_RecordsAttackerAndDamage()
{
// Arrange
var attacker = Substitute.For<IPokemon>();
var attacker = Substitute.For<IBattlePokemon>();
var user = CreateUserHitBy(attacker, 40);
// Assert
@@ -184,7 +184,7 @@ public class CounterTests
public async Task CounterHelperEffect_SpecialHit_IsNotRecorded()
{
// Arrange
var attacker = Substitute.For<IPokemon>();
var attacker = Substitute.For<IBattlePokemon>();
var user = CreateUserHitBy(attacker, 40, false);
// Assert

View File

@@ -12,17 +12,17 @@ namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves;
/// </summary>
public class CovetTests
{
private static (Covet script, IExecutingMove move, IPokemon user, IPokemon target) CreateTestSetup(
private static (Covet script, IExecutingMove move, IBattlePokemon user, IBattlePokemon target) CreateTestSetup(
IItem? targetItem)
{
var script = new Covet();
var move = Substitute.For<IExecutingMove>();
var user = Substitute.For<IPokemon>();
var user = Substitute.For<IBattlePokemon>();
// Explicitly return null to suppress NSubstitute's auto-substitution; Covet must see an empty-handed user.
user.HeldItem.Returns((IItem?)null);
move.User.Returns(user);
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
target.HeldItem.Returns(targetItem);
if (targetItem != null)
{

View File

@@ -26,13 +26,11 @@ public class CraftyShieldTests
var battle = Substitute.For<IBattle>();
battle.Sides.Returns(new[] { side });
var battleData = Substitute.For<IPokemonBattleData>();
battleData.Battle.Returns(battle);
battleData.SideIndex.Returns((byte)0);
var user = Substitute.For<IPokemon>();
user.BattleData.Returns(battleData);
var user = Substitute.For<IBattlePokemon>();
var move = Substitute.For<IExecutingMove>();
user.SideIndex.Returns((byte)0);
user.Battle.Returns(battle);
move.User.Returns(user);
return (script, move, sideVolatile);
@@ -58,7 +56,7 @@ public class CraftyShieldTests
var (script, move, sideVolatile) = CreateTestSetup();
// Act
script.OnSecondaryEffect(move, Substitute.For<IPokemon>(), 0);
script.OnSecondaryEffect(move, Substitute.For<IBattlePokemon>(), 0);
// Assert
await Assert.That(sideVolatile.Contains(ScriptUtils.ResolveName<CraftyShieldEffect>())).IsTrue();
@@ -99,21 +97,4 @@ public class CraftyShieldTests
// Assert
await Assert.That(stop).IsFalse();
}
/// <summary>
/// Technical test: outside of battle (no battle data) no shield can be raised and nothing happens.
/// </summary>
[Test]
public async Task OnSecondaryEffect_NoBattleData_DoesNothing()
{
// Arrange
var (script, move, sideVolatile) = CreateTestSetup();
move.User.BattleData.Returns((IPokemonBattleData?)null);
// Act
script.OnSecondaryEffect(move, Substitute.For<IPokemon>(), 0);
// Assert - no effect is added
await Assert.That(sideVolatile.Contains(ScriptUtils.ResolveName<CraftyShieldEffect>())).IsFalse();
}
}

View File

@@ -11,12 +11,12 @@ namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves;
/// </summary>
public class CrushGripTests
{
private static (CrushGrip script, IExecutingMove move, IPokemon target) CreateTestSetup(uint currentHealth,
private static (CrushGrip script, IExecutingMove move, IBattlePokemon target) CreateTestSetup(uint currentHealth,
uint maxHealth)
{
var script = new CrushGrip();
var move = Substitute.For<IExecutingMove>();
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
target.CurrentHealth.Returns(currentHealth);
target.BoostedStats.Returns(new StatisticSet<uint>(maxHealth, 1, 1, 1, 1, 1));
return (script, move, target);

View File

@@ -16,8 +16,8 @@ namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves;
/// </summary>
public class CurseTests
{
private static (Curse script, IExecutingMove move, IPokemon user, IPokemon target, IScriptSet targetVolatile)
CreateTestSetup(bool userIsGhost, uint userMaxHealth = 100, uint userCurrentHealth = 100)
private static (Curse script, IExecutingMove move, IBattlePokemon user, IBattlePokemon target, IScriptSet
targetVolatile) CreateTestSetup(bool userIsGhost, uint userMaxHealth = 100, uint userCurrentHealth = 100)
{
var script = new Curse();
var library = LibraryHelpers.LoadLibrary();
@@ -26,18 +26,16 @@ public class CurseTests
var battle = Substitute.For<IBattle>();
battle.Library.Returns(library);
var battleData = Substitute.For<IPokemonBattleData>();
battleData.Battle.Returns(battle);
var user = Substitute.For<IPokemon>();
user.BattleData.Returns(battleData);
var user = Substitute.For<IBattlePokemon>();
user.Types.Returns(new[] { userIsGhost ? ghostType : normalType });
user.Battle.Returns(battle);
user.MaxHealth.Returns(userMaxHealth);
user.CurrentHealth.Returns(userCurrentHealth);
var move = Substitute.For<IExecutingMove>();
move.User.Returns(user);
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
// Use a real script set so the curse applied to the target can be inspected afterwards.
var targetVolatile = new ScriptSet(target);
target.Volatile.Returns(targetVolatile);
@@ -49,7 +47,7 @@ public class CurseTests
/// <summary>
/// Helper that checks whether a stat boost change was applied to the given Pokémon.
/// </summary>
private static bool ReceivedStatBoost(IPokemon pokemon, Statistic stat, sbyte amount) =>
private static bool ReceivedStatBoost(IBattlePokemon pokemon, Statistic stat, sbyte amount) =>
pokemon.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "ChangeStatBoost" &&
(Statistic)c.GetArguments()[0]! == stat &&
(sbyte)c.GetArguments()[1]! == amount);
@@ -57,7 +55,7 @@ public class CurseTests
/// <summary>
/// Helper to extract the damage amount from a substitute's received Damage calls.
/// </summary>
private static uint? GetDamageAmount(IPokemon pokemon)
private static uint? GetDamageAmount(IBattlePokemon pokemon)
{
var call = pokemon.ReceivedCalls().FirstOrDefault(c => c.GetMethodInfo().Name == "Damage");
return call != null ? (uint)call.GetArguments()[0]! : null;
@@ -178,7 +176,7 @@ public class CurseTests
public async Task GhostCurseEffect_OnEndTurnAtFullHp_CursedPokemonLosesQuarterOfMaximumHp()
{
// Arrange
var cursed = Substitute.For<IPokemon>();
var cursed = Substitute.For<IBattlePokemon>();
cursed.MaxHealth.Returns(100u);
cursed.CurrentHealth.Returns(100u);
var effect = new GhostCurseEffect(cursed);
@@ -198,7 +196,7 @@ public class CurseTests
public async Task GhostCurseEffect_OnEndTurnAtLowHp_CursedPokemonStillLosesQuarterOfMaximumHp()
{
// Arrange
var cursed = Substitute.For<IPokemon>();
var cursed = Substitute.For<IBattlePokemon>();
cursed.MaxHealth.Returns(100u);
cursed.CurrentHealth.Returns(40u);
var effect = new GhostCurseEffect(cursed);

View File

@@ -20,7 +20,7 @@ public class DarkestLariatTests
// Arrange
var script = new DarkestLariat();
var move = Substitute.For<IExecutingMove>();
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
var bypass = false;
// Act
@@ -40,7 +40,7 @@ public class DarkestLariatTests
// Arrange
var script = new DarkestLariat();
var move = Substitute.For<IExecutingMove>();
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
var bypass = false;
// Act

View File

@@ -19,7 +19,7 @@ public class DefogTests
/// <summary>
/// Creates a fully mocked test setup where the target is on side 1 and the user on side 0.
/// </summary>
private static (Defog script, IExecutingMove move, IPokemon target, IScriptSet targetSideScripts, IScriptSet
private static (Defog script, IExecutingMove move, IBattlePokemon target, IScriptSet targetSideScripts, IScriptSet
userSideScripts) CreateTestSetup()
{
var script = new Defog();
@@ -35,18 +35,14 @@ public class DefogTests
var battle = Substitute.For<IBattle>();
battle.Sides.Returns(new[] { userSide, targetSide });
var user = Substitute.For<IPokemon>();
var userBattleData = Substitute.For<IPokemonBattleData>();
userBattleData.Battle.Returns(battle);
userBattleData.SideIndex.Returns((byte)0);
user.BattleData.Returns(userBattleData);
var user = Substitute.For<IBattlePokemon>();
user.Battle.Returns(battle);
user.SideIndex.Returns((byte)0);
move.User.Returns(user);
var target = Substitute.For<IPokemon>();
var targetBattleData = Substitute.For<IPokemonBattleData>();
targetBattleData.Battle.Returns(battle);
targetBattleData.SideIndex.Returns((byte)1);
target.BattleData.Returns(targetBattleData);
var target = Substitute.For<IBattlePokemon>();
target.Battle.Returns(battle);
target.SideIndex.Returns((byte)1);
return (script, move, target, targetSideScripts, userSideScripts);
}
@@ -144,21 +140,4 @@ public class DefogTests
c.GetMethodInfo().Name == "ChangeStatBoost" && (Statistic)c.GetArguments()[0]! == Statistic.Evasion &&
(sbyte)c.GetArguments()[1]! == -1)).IsTrue();
}
/// <summary>
/// Technical test: without battle data (outside of battle) the secondary effect does nothing and does
/// not throw.
/// </summary>
[Test]
public async Task OnSecondaryEffect_TargetHasNoBattleData_DoesNotThrow()
{
// Arrange
var script = new Defog();
var move = Substitute.For<IExecutingMove>();
var target = Substitute.For<IPokemon>();
target.BattleData.Returns((IPokemonBattleData?)null);
// Act & Assert
await Assert.That(() => script.OnSecondaryEffect(move, target, 0)).ThrowsNothing();
}
}

View File

@@ -17,14 +17,15 @@ namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves;
public class DestinyBondTests
{
/// <summary>
/// Creates a mocked user whose <see cref="IPokemon.Volatile"/> is a real <see cref="ScriptSet"/> so
/// Creates a mocked user whose <see cref="IBattlePokemon.Volatile"/> is a real <see cref="ScriptSet"/> so
/// the volatile added by the move can be inspected.
/// </summary>
private static (DestinyBond script, IExecutingMove move, IPokemon user, ScriptSet userVolatile) CreateTestSetup()
private static (DestinyBond script, IExecutingMove move, IBattlePokemon user, ScriptSet userVolatile)
CreateTestSetup()
{
var script = new DestinyBond();
var move = Substitute.For<IExecutingMove>();
var user = Substitute.For<IPokemon>();
var user = Substitute.For<IBattlePokemon>();
var userVolatile = new ScriptSet(user);
user.Volatile.Returns(userVolatile);
user.GetScripts().Returns(_ => new ScriptIterator(new List<IEnumerable<ScriptContainer>>()));
@@ -36,7 +37,7 @@ public class DestinyBondTests
/// Creates a fainting Pokémon whose battle's <see cref="BattleChoiceQueue.LastRanChoice"/> is a move
/// choice made by the given attacker.
/// </summary>
private static IPokemon CreateFaintingPokemonAttackedBy(IPokemon attacker)
private static IBattlePokemon CreateFaintingPokemonAttackedBy(IBattlePokemon attacker)
{
var moveChoice = Substitute.For<IMoveChoice>();
moveChoice.User.Returns(attacker);
@@ -45,10 +46,8 @@ public class DestinyBondTests
var battle = Substitute.For<IBattle>();
battle.ChoiceQueue.Returns(queue);
var battleData = Substitute.For<IPokemonBattleData>();
battleData.Battle.Returns(battle);
var pokemon = Substitute.For<IPokemon>();
pokemon.BattleData.Returns(battleData);
var pokemon = Substitute.For<IBattlePokemon>();
pokemon.Battle.Returns(battle);
return pokemon;
}
@@ -61,7 +60,7 @@ public class DestinyBondTests
{
// Arrange
var (script, move, _, userVolatile) = CreateTestSetup();
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
// Act
script.OnSecondaryEffect(move, target, 0);
@@ -119,7 +118,7 @@ public class DestinyBondTests
{
// Arrange
var effect = new DestinyBondEffect();
var attacker = Substitute.For<IPokemon>();
var attacker = Substitute.For<IBattlePokemon>();
attacker.BoostedStats.Returns(new StatisticSet<uint>(100, 1, 1, 1, 1, 1));
var pokemon = CreateFaintingPokemonAttackedBy(attacker);
@@ -141,7 +140,7 @@ public class DestinyBondTests
{
// Arrange
var effect = new DestinyBondEffect();
var attacker = Substitute.For<IPokemon>();
var attacker = Substitute.For<IBattlePokemon>();
attacker.BoostedStats.Returns(new StatisticSet<uint>(100, 1, 1, 1, 1, 1));
var pokemon = CreateFaintingPokemonAttackedBy(attacker);
@@ -162,7 +161,7 @@ public class DestinyBondTests
{
// Arrange
var effect = new DestinyBondEffect();
var attacker = Substitute.For<IPokemon>();
var attacker = Substitute.For<IBattlePokemon>();
attacker.BoostedStats.Returns(new StatisticSet<uint>(100, 1, 1, 1, 1, 1));
var pokemon = CreateFaintingPokemonAttackedBy(attacker);
@@ -181,7 +180,7 @@ public class DestinyBondTests
public async Task OnBeforeMove_UserMovesAgain_RemovesDestinyBondEffect()
{
// Arrange
var user = Substitute.For<IPokemon>();
var user = Substitute.For<IBattlePokemon>();
var userVolatile = new ScriptSet(user);
user.GetScripts().Returns(_ => new ScriptIterator(new List<IEnumerable<ScriptContainer>>()));
var effect = new DestinyBondEffect();
@@ -194,19 +193,4 @@ public class DestinyBondTests
// Assert
await Assert.That(userVolatile.Contains(ScriptUtils.ResolveName<DestinyBondEffect>())).IsFalse();
}
/// <summary>
/// Technical test: fainting without battle data (outside of battle) does nothing and does not throw.
/// </summary>
[Test]
public async Task OnFaint_NoBattleData_DoesNotThrow()
{
// Arrange
var effect = new DestinyBondEffect();
var pokemon = Substitute.For<IPokemon>();
pokemon.BattleData.Returns((IPokemonBattleData?)null);
// Act & Assert
await Assert.That(() => effect.OnFaint(pokemon, DamageSource.MoveDamage)).ThrowsNothing();
}
}

View File

@@ -18,12 +18,12 @@ namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves;
/// </summary>
public class DigTests
{
private static (Dig script, IExecutingMove move, IPokemon user, ScriptSet userVolatile, IMoveChoice moveChoice)
CreateTestSetup()
private static (Dig script, IExecutingMove move, IBattlePokemon user, ScriptSet userVolatile, IMoveChoice moveChoice
) CreateTestSetup()
{
var script = new Dig();
var move = Substitute.For<IExecutingMove>();
var user = Substitute.For<IPokemon>();
var user = Substitute.For<IBattlePokemon>();
// Use a real script set so the charge volatile added by Dig can be inspected afterwards.
var userVolatile = new ScriptSet(user);
user.Volatile.Returns(userVolatile);
@@ -35,9 +35,7 @@ public class DigTests
var battle = Substitute.For<IBattle>();
battle.EventHook.Returns(new EventHook());
var battleData = Substitute.For<IPokemonBattleData>();
battleData.Battle.Returns(battle);
user.BattleData.Returns(battleData);
user.Battle.Returns(battle);
return (script, move, user, userVolatile, moveChoice);
}
@@ -250,7 +248,7 @@ public class DigTests
public async Task OnAfterMoveChoice_ChoiceIsNotDigCharge_RemovesDigEffect()
{
// Arrange
var user = Substitute.For<IPokemon>();
var user = Substitute.For<IBattlePokemon>();
var userVolatile = new ScriptSet(user);
user.GetScripts().Returns(_ => new ScriptIterator(new List<IEnumerable<ScriptContainer>>()));
var effect = new DigEffect(user);
@@ -273,7 +271,7 @@ public class DigTests
public async Task OnAfterMoveChoice_ChoiceIsDigCharge_KeepsDigEffect()
{
// Arrange
var user = Substitute.For<IPokemon>();
var user = Substitute.For<IBattlePokemon>();
var userVolatile = new ScriptSet(user);
user.GetScripts().Returns(_ => new ScriptIterator(new List<IEnumerable<ScriptContainer>>()));
var effect = new DigEffect(user);

View File

@@ -36,26 +36,22 @@ public class DisableTests
/// Creates a fully mocked test setup where the target's volatile scripts are a real
/// <see cref="ScriptSet"/> and its last used move is the given one (or none).
/// </summary>
private static (Disable script, IExecutingMove move, IPokemon target, ScriptSet targetVolatile, IHitData hitData)
CreateTestSetup(string? lastUsedMove, bool lastMoveIsStruggle = false)
private static (Disable script, IExecutingMove move, IBattlePokemon target, ScriptSet targetVolatile, IHitData
hitData) CreateTestSetup(string? lastUsedMove, bool lastMoveIsStruggle = false)
{
var script = new Disable();
var move = Substitute.For<IExecutingMove>();
var user = Substitute.For<IPokemon>();
var userBattleData = Substitute.For<IPokemonBattleData>();
user.BattleData.Returns(userBattleData);
var user = Substitute.For<IBattlePokemon>();
move.User.Returns(user);
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
var targetVolatile = new ScriptSet(target);
target.Volatile.Returns(targetVolatile);
target.GetScripts().Returns(_ => new ScriptIterator(new List<IEnumerable<ScriptContainer>>()));
var targetBattleData = Substitute.For<IPokemonBattleData>();
// Create the choice before the Returns call; configuring substitutes inside Returns is not allowed.
var lastMoveChoice = lastUsedMove == null ? null : CreateMoveChoice(lastUsedMove);
targetBattleData.LastMoveChoice.Returns(lastMoveChoice);
target.BattleData.Returns(targetBattleData);
target.LastMoveChoice.Returns(lastMoveChoice);
// Struggle is recognized through the misc library's replacement choice check, not by name.
var miscLibrary = Substitute.For<IMiscLibrary>();
@@ -144,24 +140,6 @@ public class DisableTests
await Assert.That(targetVolatile.Contains(ScriptUtils.ResolveName<DisableEffect>())).IsFalse();
}
/// <summary>
/// Technical test: without battle data on the user (outside of battle) the effect does nothing and
/// does not throw.
/// </summary>
[Test]
public async Task OnSecondaryEffect_UserHasNoBattleData_DoesNothing()
{
// Arrange
var (script, move, target, targetVolatile, _) = CreateTestSetup("tackle");
move.User.BattleData.Returns((IPokemonBattleData?)null);
// Act
script.OnSecondaryEffect(move, target, 0);
// Assert
await Assert.That(targetVolatile.Contains(ScriptUtils.ResolveName<DisableEffect>())).IsFalse();
}
/// <summary>
/// Bulbapedia: "Disable temporarily prevents the target from using a specific move." Selecting the
/// disabled move is prevented by the <see cref="DisableEffect"/>.
@@ -207,7 +185,7 @@ public class DisableTests
public async Task OnEndTurn_ThreeTurnsPassed_EffectStillActive()
{
// Arrange
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
var targetVolatile = new ScriptSet(target);
target.GetScripts().Returns(_ => new ScriptIterator(new List<IEnumerable<ScriptContainer>>()));
var effect = new DisableEffect(new StringKey("tackle"));
@@ -230,7 +208,7 @@ public class DisableTests
public async Task OnEndTurn_FourTurnsPassed_EffectRemovesItself()
{
// Arrange
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
var targetVolatile = new ScriptSet(target);
target.GetScripts().Returns(_ => new ScriptIterator(new List<IEnumerable<ScriptContainer>>()));
var effect = new DisableEffect(new StringKey("tackle"));

View File

@@ -18,12 +18,12 @@ namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves;
/// </summary>
public class DiveTests
{
private static (Dive script, IExecutingMove move, IPokemon user, ScriptSet userVolatile, IMoveChoice moveChoice)
CreateTestSetup()
private static (Dive script, IExecutingMove move, IBattlePokemon user, ScriptSet userVolatile, IMoveChoice
moveChoice) CreateTestSetup()
{
var script = new Dive();
var move = Substitute.For<IExecutingMove>();
var user = Substitute.For<IPokemon>();
var user = Substitute.For<IBattlePokemon>();
// Use a real script set so the charge volatile added by Dive can be inspected afterwards.
var userVolatile = new ScriptSet(user);
user.Volatile.Returns(userVolatile);
@@ -35,9 +35,7 @@ public class DiveTests
var battle = Substitute.For<IBattle>();
battle.EventHook.Returns(new EventHook());
var battleData = Substitute.For<IPokemonBattleData>();
battleData.Battle.Returns(battle);
user.BattleData.Returns(battleData);
user.Battle.Returns(battle);
return (script, move, user, userVolatile, moveChoice);
}
@@ -126,7 +124,7 @@ public class DiveTests
// Arrange
var (script, move, user, userVolatile, _) = CreateTestSetup();
userVolatile.Add(new DiveEffect(user));
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
// Act
script.OnSecondaryEffect(move, target, 0);
@@ -248,7 +246,7 @@ public class DiveTests
public async Task OnAfterMoveChoice_ChoiceIsNotDiveCharge_RemovesDiveEffect()
{
// Arrange
var user = Substitute.For<IPokemon>();
var user = Substitute.For<IBattlePokemon>();
var userVolatile = new ScriptSet(user);
user.GetScripts().Returns(_ => new ScriptIterator(new List<IEnumerable<ScriptContainer>>()));
var effect = new DiveEffect(user);
@@ -271,7 +269,7 @@ public class DiveTests
public async Task OnAfterMoveChoice_ChoiceIsDiveCharge_KeepsDiveEffect()
{
// Arrange
var user = Substitute.For<IPokemon>();
var user = Substitute.For<IBattlePokemon>();
var userVolatile = new ScriptSet(user);
user.GetScripts().Returns(_ => new ScriptIterator(new List<IEnumerable<ScriptContainer>>()));
var effect = new DiveEffect(user);

View File

@@ -18,8 +18,8 @@ public class DoomDesireTests
/// Creates a fully mocked test setup where the target is at the given position on a side whose
/// volatile scripts are a real <see cref="ScriptSet"/>.
/// </summary>
private static (DoomDesire script, IExecutingMove move, IPokemon target, IBattleSide side, ScriptSet sideScripts,
IHitData hitData) CreateTestSetup(byte position = 0, uint damage = 100)
private static (DoomDesire script, IExecutingMove move, IBattlePokemon target, IBattleSide side, ScriptSet
sideScripts, IHitData hitData) CreateTestSetup(byte position = 0, uint damage = 100)
{
var script = new DoomDesire();
var move = Substitute.For<IExecutingMove>();
@@ -31,13 +31,11 @@ public class DoomDesireTests
var battle = Substitute.For<IBattle>();
battle.Sides.Returns(new[] { side });
var target = Substitute.For<IPokemon>();
var battleData = Substitute.For<IPokemonBattleData>();
battleData.Battle.Returns(battle);
battleData.SideIndex.Returns((byte)0);
battleData.Position.Returns(position);
target.BattleData.Returns(battleData);
side.Pokemon.Returns(new IPokemon?[] { target });
var target = Substitute.For<IBattlePokemon>();
target.Battle.Returns(battle);
target.SideIndex.Returns((byte)0);
target.Position.Returns(position);
side.Pokemon.Returns(new IBattlePokemon?[] { target });
var hitData = Substitute.For<IHitData>();
hitData.Damage.Returns(damage);
@@ -175,25 +173,4 @@ public class DoomDesireTests
// Assert
await Assert.That(sideScripts.Contains(ScriptUtils.ResolveName<DoomDesireEffect>())).IsFalse();
}
/// <summary>
/// Technical test: without battle data on the target (outside of battle) the script does nothing and
/// does not throw.
/// </summary>
[Test]
public async Task BlockOutgoingHit_TargetHasNoBattleData_DoesNotBlock()
{
// Arrange
var script = new DoomDesire();
var move = Substitute.For<IExecutingMove>();
var target = Substitute.For<IPokemon>();
target.BattleData.Returns((IPokemonBattleData?)null);
var block = false;
// Act
script.BlockOutgoingHit(move, target, 0, ref block);
// Assert
await Assert.That(block).IsFalse();
}
}

View File

@@ -20,8 +20,8 @@ public class DoublePowerIfTargetDamagedInTurnTests
/// Creates a fully mocked test setup where the target sits on side 0 whose volatile scripts are a
/// real <see cref="ScriptSet"/>.
/// </summary>
private static (DoublePowerIfTargetDamagedInTurn script, IExecutingMove move, IPokemon target, ScriptSet sideScripts
) CreateTestSetup()
private static (DoublePowerIfTargetDamagedInTurn script, IExecutingMove move, IBattlePokemon target, ScriptSet
sideScripts ) CreateTestSetup()
{
var script = new DoublePowerIfTargetDamagedInTurn();
var move = Substitute.For<IExecutingMove>();
@@ -33,17 +33,13 @@ public class DoublePowerIfTargetDamagedInTurnTests
var battle = Substitute.For<IBattle>();
battle.Sides.Returns(new[] { side });
var user = Substitute.For<IPokemon>();
var userBattleData = Substitute.For<IPokemonBattleData>();
userBattleData.Battle.Returns(battle);
user.BattleData.Returns(userBattleData);
var user = Substitute.For<IBattlePokemon>();
user.Battle.Returns(battle);
move.User.Returns(user);
var target = Substitute.For<IPokemon>();
var targetBattleData = Substitute.For<IPokemonBattleData>();
targetBattleData.Battle.Returns(battle);
targetBattleData.SideIndex.Returns((byte)0);
target.BattleData.Returns(targetBattleData);
var target = Substitute.For<IBattlePokemon>();
target.Battle.Returns(battle);
target.SideIndex.Returns((byte)0);
return (script, move, target, sideScripts);
}
@@ -140,7 +136,7 @@ public class DoublePowerIfTargetDamagedInTurnTests
var (script, move, target, sideScripts) = CreateTestSetup();
var data = new DoublePowerIfTargetDamagedInTurnData();
sideScripts.Add(data);
data.OnDamage(Substitute.For<IPokemon>(), DamageSource.MoveDamage, 100, 80);
data.OnDamage(Substitute.For<IBattlePokemon>(), DamageSource.MoveDamage, 100, 80);
ushort basePower = 60;
// Act
@@ -192,27 +188,4 @@ public class DoublePowerIfTargetDamagedInTurnTests
await Assert.That(sideScripts.Contains(ScriptUtils.ResolveName<DoublePowerIfTargetDamagedInTurnData>()))
.IsFalse();
}
/// <summary>
/// Technical test: without battle data on the user (outside of battle) the base power is unchanged
/// and nothing throws.
/// </summary>
[Test]
public async Task ChangeBasePower_UserHasNoBattleData_BasePowerUnchanged()
{
// Arrange
var script = new DoublePowerIfTargetDamagedInTurn();
var move = Substitute.For<IExecutingMove>();
var user = Substitute.For<IPokemon>();
user.BattleData.Returns((IPokemonBattleData?)null);
move.User.Returns(user);
var target = Substitute.For<IPokemon>();
ushort basePower = 60;
// Act
script.ChangeBasePower(move, target, 0, ref basePower);
// Assert
await Assert.That(basePower).IsEqualTo((ushort)60);
}
}

View File

@@ -12,11 +12,11 @@ namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves;
/// </summary>
public class DragonAscentTests
{
private static (DragonAscent script, IExecutingMove move, IPokemon user) CreateTestSetup()
private static (DragonAscent script, IExecutingMove move, IBattlePokemon user) CreateTestSetup()
{
var script = new DragonAscent();
var move = Substitute.For<IExecutingMove>();
var user = Substitute.For<IPokemon>();
var user = Substitute.For<IBattlePokemon>();
move.User.Returns(user);
return (script, move, user);
}
@@ -25,7 +25,7 @@ public class DragonAscentTests
/// Helper checking whether the given Pokémon received a ChangeStatBoost call for the given stat with
/// a one-stage self-inflicted, non-forced drop.
/// </summary>
private static bool ReceivedSelfInflictedDrop(IPokemon pokemon, Statistic stat) =>
private static bool ReceivedSelfInflictedDrop(IBattlePokemon pokemon, Statistic stat) =>
pokemon.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "ChangeStatBoost" &&
(Statistic)c.GetArguments()[0]! == stat && (sbyte)c.GetArguments()[1]! == -1 &&
(bool)c.GetArguments()[2]! && !(bool)c.GetArguments()[3]!);
@@ -39,7 +39,7 @@ public class DragonAscentTests
{
// Arrange
var (script, move, user) = CreateTestSetup();
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
// Act
script.OnSecondaryEffect(move, target, 0);
@@ -57,7 +57,7 @@ public class DragonAscentTests
{
// Arrange
var (script, move, user) = CreateTestSetup();
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
// Act
script.OnSecondaryEffect(move, target, 0);
@@ -75,7 +75,7 @@ public class DragonAscentTests
{
// Arrange
var (script, move, _) = CreateTestSetup();
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
// Act
script.OnSecondaryEffect(move, target, 0);
@@ -93,7 +93,7 @@ public class DragonAscentTests
{
// Arrange
var (script, move, user) = CreateTestSetup();
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
// Act
script.OnSecondaryEffect(move, target, 0);

View File

@@ -15,12 +15,12 @@ public class DrainTests
/// <summary>
/// Creates a fully mocked test setup for Drain tests.
/// </summary>
private static (Drain drain, IExecutingMove move, IPokemon target, IPokemon user) CreateTestSetup(uint damage,
bool holdsBigRoot = false, Script[]? targetScripts = null)
private static (Drain drain, IExecutingMove move, IBattlePokemon target, IBattlePokemon user) CreateTestSetup(
uint damage, bool holdsBigRoot = false, Script[]? targetScripts = null)
{
var drain = new Drain();
var move = Substitute.For<IExecutingMove>();
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
var hitData = Substitute.For<IHitData>();
hitData.Damage.Returns(damage);
move.GetHitData(target, 0).Returns(hitData);
@@ -31,7 +31,7 @@ public class DrainTests
.ToArray();
target.GetScripts().Returns(_ => new ScriptIterator(containers));
var user = Substitute.For<IPokemon>();
var user = Substitute.For<IBattlePokemon>();
if (holdsBigRoot)
user.HasHeldItem("big_root").Returns(true);
move.User.Returns(user);
@@ -42,7 +42,7 @@ public class DrainTests
/// <summary>
/// Helper to extract the heal amount from the user's received Heal calls.
/// </summary>
private static uint? GetHealAmount(IPokemon user)
private static uint? GetHealAmount(IBattlePokemon user)
{
var call = user.ReceivedCalls().FirstOrDefault(c => c.GetMethodInfo().Name == "Heal");
return call != null ? (uint)call.GetArguments()[0]! : null;
@@ -51,7 +51,7 @@ public class DrainTests
/// <summary>
/// Helper to extract the damage amount from the user's received Damage calls.
/// </summary>
private static uint? GetDamageAmount(IPokemon user)
private static uint? GetDamageAmount(IBattlePokemon user)
{
var call = user.ReceivedCalls().FirstOrDefault(c => c.GetMethodInfo().Name == "Damage");
return call != null ? (uint)call.GetArguments()[0]! : null;
@@ -60,7 +60,7 @@ public class DrainTests
/// <summary>
/// Helper to extract the damage source from the user's received Damage calls.
/// </summary>
private static DamageSource? GetDamageSource(IPokemon user)
private static DamageSource? GetDamageSource(IBattlePokemon user)
{
var call = user.ReceivedCalls().FirstOrDefault(c => c.GetMethodInfo().Name == "Damage");
return call != null ? (DamageSource)call.GetArguments()[1]! : null;

View File

@@ -17,12 +17,12 @@ public class DreamEaterTests
/// <summary>
/// Creates a fully mocked test setup for Dream Eater tests.
/// </summary>
private static (DreamEater script, IExecutingMove move, IPokemon target, IPokemon user) CreateTestSetup(uint damage,
bool holdsBigRoot = false, Script[]? targetScripts = null)
private static (DreamEater script, IExecutingMove move, IBattlePokemon target, IBattlePokemon user) CreateTestSetup(
uint damage, bool holdsBigRoot = false, Script[]? targetScripts = null)
{
var script = new DreamEater();
var move = Substitute.For<IExecutingMove>();
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
var hitData = Substitute.For<IHitData>();
hitData.Damage.Returns(damage);
move.GetHitData(target, 0).Returns(hitData);
@@ -33,7 +33,7 @@ public class DreamEaterTests
.ToArray();
target.GetScripts().Returns(_ => new ScriptIterator(containers));
var user = Substitute.For<IPokemon>();
var user = Substitute.For<IBattlePokemon>();
if (holdsBigRoot)
user.HasHeldItem("big_root").Returns(true);
move.User.Returns(user);
@@ -44,7 +44,7 @@ public class DreamEaterTests
/// <summary>
/// Helper to extract the heal amount from the user's received Heal calls.
/// </summary>
private static uint? GetHealAmount(IPokemon user)
private static uint? GetHealAmount(IBattlePokemon user)
{
var call = user.ReceivedCalls().FirstOrDefault(c => c.GetMethodInfo().Name == "Heal");
return call != null ? (uint)call.GetArguments()[0]! : null;

View File

@@ -35,11 +35,12 @@ public class EchoedVoiceTests
/// Creates a fully mocked test setup where the user's side has a real <see cref="ScriptSet"/> as its
/// volatile scripts.
/// </summary>
private static (EchoedVoice script, IExecutingMove move, IPokemon target, IScriptSet sideScripts) CreateTestSetup()
private static (EchoedVoice script, IExecutingMove move, IBattlePokemon target, IScriptSet sideScripts)
CreateTestSetup()
{
var script = new EchoedVoice();
var move = Substitute.For<IExecutingMove>();
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
var side = Substitute.For<IBattleSide>();
side.GetScripts().Returns(_ => new ScriptIterator(Array.Empty<IEnumerable<ScriptContainer>>()));
@@ -49,13 +50,10 @@ public class EchoedVoiceTests
var battle = Substitute.For<IBattle>();
battle.Sides.Returns(new[] { side });
var battleData = Substitute.For<IPokemonBattleData>();
battleData.Battle.Returns(battle);
battleData.SideIndex.Returns((byte)0);
var user = Substitute.For<IPokemon>();
user.BattleData.Returns(battleData);
var user = Substitute.For<IBattlePokemon>();
move.User.Returns(user);
user.SideIndex.Returns((byte)0);
user.Battle.Returns(battle);
return (script, move, target, sideScripts);
}
@@ -155,25 +153,6 @@ public class EchoedVoiceTests
await Assert.That(sideScripts.Get<EchoedVoiceData>()!.Stacks).IsEqualTo(2);
}
/// <summary>
/// Technical test: without battle data on the user (outside of battle) the base power hook does
/// nothing and does not throw.
/// </summary>
[Test]
public async Task ChangeBasePower_UserHasNoBattleData_BasePowerUnchanged()
{
// Arrange
var (script, move, target, _) = CreateTestSetup();
move.User.BattleData.Returns((IPokemonBattleData?)null);
ushort basePower = 40;
// Act
script.ChangeBasePower(move, target, 0, ref basePower);
// Assert
await Assert.That(basePower).IsEqualTo((ushort)40);
}
/// <summary>
/// Bulbapedia: the boost only applies on "consecutive" turns — when a Pokémon on the side chooses a
/// move other than Echoed Voice, the <see cref="EchoedVoiceData"/> marker removes itself, resetting

View File

@@ -18,12 +18,10 @@ public class ElectricTerrainTests
var move = Substitute.For<IExecutingMove>();
var battle = Substitute.For<IBattle>();
var battleData = Substitute.For<IPokemonBattleData>();
battleData.Battle.Returns(battle);
var user = Substitute.For<IPokemon>();
user.BattleData.Returns(battleData);
var user = Substitute.For<IBattlePokemon>();
move.User.Returns(user);
user.Battle.Returns(battle);
return (script, move, battle);
}
@@ -40,27 +38,9 @@ public class ElectricTerrainTests
var (script, move, battle) = CreateTestSetup();
// Act
script.OnSecondaryEffect(move, Substitute.For<IPokemon>(), 0);
script.OnSecondaryEffect(move, Substitute.For<IBattlePokemon>(), 0);
// Assert
battle.Received(1).SetTerrain(ScriptUtils.ResolveName<ElectricTerrainScript>());
}
/// <summary>
/// Technical test: without battle data on the user (outside of battle) the effect does nothing and
/// does not throw.
/// </summary>
[Test]
public void OnSecondaryEffect_UserHasNoBattleData_DoesNotSetTerrain()
{
// Arrange
var (script, move, battle) = CreateTestSetup();
move.User.BattleData.Returns((IPokemonBattleData?)null);
// Act
script.OnSecondaryEffect(move, Substitute.For<IPokemon>(), 0);
// Assert
battle.DidNotReceiveWithAnyArgs().SetTerrain(default);
}
}

View File

@@ -20,12 +20,12 @@ public class ElectrifyTests
/// <see cref="BattleChoiceQueue"/>. When <paramref name="targetStillHasChoice"/> is false, the queue
/// only contains another Pokémon's choice, simulating a target that already moved this turn.
/// </summary>
private static (Electrify script, IExecutingMove move, IPokemon target, IScriptSet choiceVolatile, IHitData hitData)
CreateTestSetup(bool targetStillHasChoice = true, bool hasQueue = true)
private static (Electrify script, IExecutingMove move, IBattlePokemon target, IScriptSet choiceVolatile, IHitData
hitData) CreateTestSetup(bool targetStillHasChoice = true, bool hasQueue = true)
{
var script = new Electrify();
var move = Substitute.For<IExecutingMove>();
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
var hitData = Substitute.For<IHitData>();
move.GetHitData(target, 0).Returns(hitData);
@@ -40,9 +40,7 @@ public class ElectrifyTests
var battle = Substitute.For<IBattle>();
battle.ChoiceQueue.Returns(hasQueue ? queue : null);
var battleData = Substitute.For<IPokemonBattleData>();
battleData.Battle.Returns(battle);
target.BattleData.Returns(battleData);
target.Battle.Returns(battle);
return (script, move, target, choiceVolatile, hitData);
}
@@ -111,10 +109,8 @@ public class ElectrifyTests
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);
var target = Substitute.For<IBattlePokemon>();
target.Battle.Returns(battle);
await Assert.That(library.StaticLibrary.Types.TryGetTypeIdentifier("normal", out var normal)).IsTrue();
await Assert.That(library.StaticLibrary.Types.TryGetTypeIdentifier("electric", out var electric)).IsTrue();
@@ -127,24 +123,4 @@ public class ElectrifyTests
await Assert.That(moveType).IsNotNull();
await Assert.That(moveType!.Value).IsEqualTo(electric);
}
/// <summary>
/// Technical test: without battle data on the target the move type cannot be resolved and remains
/// unchanged.
/// </summary>
[Test]
public async Task ChangeMoveType_TargetHasNoBattleData_TypeUnchanged()
{
// Arrange
var effect = new ElectrifyEffect();
var target = Substitute.For<IPokemon>();
target.BattleData.Returns((IPokemonBattleData?)null);
TypeIdentifier? moveType = null;
// Act
effect.ChangeMoveType(Substitute.For<IExecutingMove>(), target, 0, ref moveType);
// Assert
await Assert.That(moveType).IsNull();
}
}

View File

@@ -14,15 +14,15 @@ namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves;
/// </summary>
public class ElectroBallTests
{
private static (ElectroBall script, IExecutingMove move, IPokemon target) CreateTestSetup(uint userSpeed,
private static (ElectroBall script, IExecutingMove move, IBattlePokemon target) CreateTestSetup(uint userSpeed,
uint targetSpeed)
{
var script = new ElectroBall();
var move = Substitute.For<IExecutingMove>();
var user = Substitute.For<IPokemon>();
var user = Substitute.For<IBattlePokemon>();
user.BoostedStats.Returns(new StatisticSet<uint>(1, 1, 1, 1, 1, userSpeed));
move.User.Returns(user);
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
target.BoostedStats.Returns(new StatisticSet<uint>(1, 1, 1, 1, 1, targetSpeed));
return (script, move, target);
}

View File

@@ -17,9 +17,9 @@ public class EmbargoTests
/// <summary>
/// Creates a target Pokémon whose volatile scripts are a real <see cref="ScriptSet"/>.
/// </summary>
private static (IPokemon target, IScriptSet targetVolatile) CreateTarget()
private static (IBattlePokemon target, IScriptSet targetVolatile) CreateTarget()
{
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
target.GetScripts().Returns(_ => new ScriptIterator(Array.Empty<IEnumerable<ScriptContainer>>()));
IScriptSet targetVolatile = new ScriptSet(target);
target.Volatile.Returns(targetVolatile);
@@ -57,7 +57,7 @@ public class EmbargoTests
var prevented = false;
// Act
effect.PreventHeldItemConsume(Substitute.For<IPokemon>(), Substitute.For<IItem>(), ref prevented);
effect.PreventHeldItemConsume(Substitute.For<IBattlePokemon>(), Substitute.For<IItem>(), ref prevented);
// Assert
await Assert.That(prevented).IsTrue();

View File

@@ -39,8 +39,9 @@ public class EncoreTests
/// Creates a fully mocked test setup where the target's volatile scripts are a real
/// <see cref="ScriptSet"/> and its last used move is the given one (or none).
/// </summary>
private static (Encore script, IExecutingMove move, IPokemon target, IScriptSet targetVolatile, IHitData hitData)
CreateTestSetup(string? lastUsedMove, bool lastMoveIsReplacement = false, bool lastMoveCantRepeat = false)
private static (Encore script, IExecutingMove move, IBattlePokemon target, IScriptSet targetVolatile, IHitData
hitData) CreateTestSetup(string? lastUsedMove, bool lastMoveIsReplacement = false,
bool lastMoveCantRepeat = false)
{
var script = new Encore();
var move = Substitute.For<IExecutingMove>();
@@ -52,16 +53,14 @@ public class EncoreTests
var battle = Substitute.For<IBattle>();
battle.Library.Returns(library);
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
target.GetScripts().Returns(_ => new ScriptIterator(Array.Empty<IEnumerable<ScriptContainer>>()));
IScriptSet targetVolatile = new ScriptSet(target);
target.Volatile.Returns(targetVolatile);
var targetBattleData = Substitute.For<IPokemonBattleData>();
// Create the choice before the Returns call; configuring substitutes inside Returns is not allowed.
var lastMoveChoice = lastUsedMove == null ? null : CreateMoveChoice(lastUsedMove, lastMoveCantRepeat);
targetBattleData.LastMoveChoice.Returns(lastMoveChoice);
targetBattleData.Battle.Returns(battle);
target.BattleData.Returns(targetBattleData);
target.LastMoveChoice.Returns(lastMoveChoice);
target.Battle.Returns(battle);
if (lastMoveChoice != null)
miscLibrary.IsReplacementChoice(lastMoveChoice).Returns(lastMoveIsReplacement);
@@ -165,25 +164,6 @@ public class EncoreTests
await Assert.That(moveData!.HasFlag(MoveFlags.CantRepeat)).IsTrue();
}
/// <summary>
/// Technical test: without battle data on the target (outside of battle) the effect does nothing and
/// does not throw.
/// </summary>
[Test]
public async Task OnSecondaryEffect_TargetHasNoBattleData_DoesNothing()
{
// Arrange
var (script, move, target, targetVolatile, hitData) = CreateTestSetup("tackle");
target.BattleData.Returns((IPokemonBattleData?)null);
// Act
script.OnSecondaryEffect(move, target, 0);
// Assert
hitData.DidNotReceive().Fail();
await Assert.That(targetVolatile.Contains(ScriptUtils.ResolveName<EncoreEffect>())).IsFalse();
}
/// <summary>
/// Bulbapedia: "Duration standardized to exactly 3 turns" (Generation V onward). After two
/// end-of-turn ticks the effect is still active.
@@ -192,7 +172,7 @@ public class EncoreTests
public async Task OnEndTurn_TwoTurnsPassed_EffectStillActive()
{
// Arrange
var owner = Substitute.For<IPokemon>();
var owner = Substitute.For<IBattlePokemon>();
owner.GetScripts().Returns(_ => new ScriptIterator(Array.Empty<IEnumerable<ScriptContainer>>()));
IScriptSet ownerVolatile = new ScriptSet(owner);
var effect = new EncoreEffect(owner, new StringKey("tackle"), 3);
@@ -214,7 +194,7 @@ public class EncoreTests
public async Task OnEndTurn_ThreeTurnsPassed_EffectRemovesItself()
{
// Arrange
var owner = Substitute.For<IPokemon>();
var owner = Substitute.For<IBattlePokemon>();
owner.GetScripts().Returns(_ => new ScriptIterator(Array.Empty<IEnumerable<ScriptContainer>>()));
IScriptSet ownerVolatile = new ScriptSet(owner);
var effect = new EncoreEffect(owner, new StringKey("tackle"), 3);

View File

@@ -11,15 +11,15 @@ namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves;
/// </summary>
public class EndeavorTests
{
private static (Endeavor script, IExecutingMove move, IPokemon target) CreateTestSetup(uint userHealth,
private static (Endeavor script, IExecutingMove move, IBattlePokemon target) CreateTestSetup(uint userHealth,
uint targetHealth)
{
var script = new Endeavor();
var move = Substitute.For<IExecutingMove>();
var user = Substitute.For<IPokemon>();
var user = Substitute.For<IBattlePokemon>();
user.CurrentHealth.Returns(userHealth);
move.User.Returns(user);
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
target.CurrentHealth.Returns(targetHealth);
return (script, move, target);
}

View File

@@ -20,12 +20,12 @@ public class EndureTests
/// <see cref="ProtectionScript.OnSecondaryEffect"/>. The <c>target</c> of the secondary effect is the
/// Pokémon using Endure itself, as the move is self-targeted.
/// </summary>
private static (Endure script, IExecutingMove move, IPokemon target, IHitData hitData, IScriptSet volatileSet)
private static (Endure script, IExecutingMove move, IBattlePokemon target, IHitData hitData, IScriptSet volatileSet)
CreateProtectSetup(bool userMovesLast, float randomRoll)
{
var script = new Endure();
var move = Substitute.For<IExecutingMove>();
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
var hitData = Substitute.For<IHitData>();
move.GetHitData(target, 0).Returns(hitData);
@@ -39,9 +39,7 @@ public class EndureTests
battle.ChoiceQueue.Returns(queue);
battle.Random.Returns(random);
var battleData = Substitute.For<IPokemonBattleData>();
battleData.Battle.Returns(battle);
target.BattleData.Returns(battleData);
target.Battle.Returns(battle);
target.GetScripts().Returns(_ => new ScriptIterator(Array.Empty<IEnumerable<ScriptContainer>>()));
IScriptSet volatileSet = new ScriptSet(target);
@@ -134,7 +132,7 @@ public class EndureTests
{
// Arrange
var effect = new EndureEffect();
var pokemon = Substitute.For<IPokemon>();
var pokemon = Substitute.For<IBattlePokemon>();
pokemon.CurrentHealth.Returns(currentHealth);
var damage = incomingDamage;
@@ -154,7 +152,7 @@ public class EndureTests
{
// Arrange
var effect = new EndureEffect();
var pokemon = Substitute.For<IPokemon>();
var pokemon = Substitute.For<IBattlePokemon>();
pokemon.CurrentHealth.Returns(100u);
var damage = 100u;
@@ -174,7 +172,7 @@ public class EndureTests
{
// Arrange
var effect = new EndureEffect();
var pokemon = Substitute.For<IPokemon>();
var pokemon = Substitute.For<IBattlePokemon>();
pokemon.CurrentHealth.Returns(100u);
var damage = 50u;
@@ -193,7 +191,7 @@ public class EndureTests
public async Task OnEndTurn_EffectRemovesItself()
{
// Arrange
var owner = Substitute.For<IPokemon>();
var owner = Substitute.For<IBattlePokemon>();
owner.GetScripts().Returns(_ => new ScriptIterator(Array.Empty<IEnumerable<ScriptContainer>>()));
IScriptSet ownerVolatile = new ScriptSet(owner);
var effect = new EndureEffect();

View File

@@ -25,16 +25,16 @@ public class EntrainmentTests
return ability;
}
private static (Entrainment script, IExecutingMove move, IPokemon target, IHitData hitData) CreateTestSetup(
private static (Entrainment script, IExecutingMove move, IBattlePokemon target, IHitData hitData) CreateTestSetup(
IAbility? userAbility, IAbility? targetAbility)
{
var script = new Entrainment();
var move = Substitute.For<IExecutingMove>();
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
var hitData = Substitute.For<IHitData>();
move.GetHitData(target, 0).Returns(hitData);
var user = Substitute.For<IPokemon>();
var user = Substitute.For<IBattlePokemon>();
user.ActiveAbility.Returns(userAbility);
move.User.Returns(user);
target.ActiveAbility.Returns(targetAbility);

View File

@@ -12,16 +12,16 @@ namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves;
/// </summary>
public class EruptionTests
{
private static (Eruption script, IExecutingMove move, IPokemon target) CreateTestSetup(uint currentHealth,
private static (Eruption script, IExecutingMove move, IBattlePokemon target) CreateTestSetup(uint currentHealth,
uint maxHealth)
{
var script = new Eruption();
var move = Substitute.For<IExecutingMove>();
var user = Substitute.For<IPokemon>();
var user = Substitute.For<IBattlePokemon>();
user.CurrentHealth.Returns(currentHealth);
user.BoostedStats.Returns(new StatisticSet<uint>(maxHealth, 1, 1, 1, 1, 1));
move.User.Returns(user);
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
return (script, move, target);
}

View File

@@ -10,11 +10,11 @@ namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves;
/// </summary>
public class ExplosionTests
{
private static (Explosion script, IExecutingMove move, IPokemon user) CreateTestSetup(uint currentHealth)
private static (Explosion script, IExecutingMove move, IBattlePokemon user) CreateTestSetup(uint currentHealth)
{
var script = new Explosion();
var move = Substitute.For<IExecutingMove>();
var user = Substitute.For<IPokemon>();
var user = Substitute.For<IBattlePokemon>();
user.CurrentHealth.Returns(currentHealth);
move.User.Returns(user);
return (script, move, user);
@@ -23,7 +23,7 @@ public class ExplosionTests
/// <summary>
/// Helper to extract the damage amount from the user's received Damage calls.
/// </summary>
private static uint? GetDamageAmount(IPokemon user)
private static uint? GetDamageAmount(IBattlePokemon user)
{
var call = user.ReceivedCalls().FirstOrDefault(c => c.GetMethodInfo().Name == "Damage");
return call != null ? (uint)call.GetArguments()[0]! : null;
@@ -40,7 +40,7 @@ public class ExplosionTests
var (script, move, user) = CreateTestSetup(100);
// Act
script.OnAfterHits(move, Substitute.For<IPokemon>());
script.OnAfterHits(move, Substitute.For<IBattlePokemon>());
// Assert
var damage = GetDamageAmount(user);
@@ -60,7 +60,7 @@ public class ExplosionTests
var (script, move, user) = CreateTestSetup(100);
// Act
script.OnAfterHits(move, Substitute.For<IPokemon>());
script.OnAfterHits(move, Substitute.For<IBattlePokemon>());
// Assert
var call = user.ReceivedCalls().First(c => c.GetMethodInfo().Name == "Damage");

View File

@@ -16,15 +16,15 @@ public class FacadeTests
{
/// <summary>
/// Creates a fully mocked test setup where the user has the given (or no) non-volatile status in its
/// <see cref="IPokemon.StatusScript"/>, and is using a move of the given category.
/// <see cref="IBattlePokemon.StatusScript"/>, and is using a move of the given category.
/// </summary>
private static (Facade facade, IExecutingMove move, IPokemon target) CreateTestSetup(Script? status,
private static (Facade facade, IExecutingMove move, IBattlePokemon target) CreateTestSetup(Script? status,
MoveCategory category = MoveCategory.Physical)
{
var facade = new Facade();
var move = Substitute.For<IExecutingMove>();
var target = Substitute.For<IPokemon>();
var user = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
var user = Substitute.For<IBattlePokemon>();
user.StatusScript.Returns(status == null ? new ScriptContainer() : new ScriptContainer(status));
move.User.Returns(user);
var useMove = Substitute.For<IMoveData>();

View File

@@ -20,31 +20,29 @@ public class FairyLockTests
/// Creates a fully mocked test setup where the battle has a real <see cref="ScriptSet"/> as its volatile
/// script set.
/// </summary>
private static (FairyLock script, IExecutingMove move, IPokemon target, IBattle battle, IScriptSet battleVolatile)
CreateTestSetup()
private static (FairyLock script, IExecutingMove move, IBattlePokemon target, IBattle battle, IScriptSet
battleVolatile) CreateTestSetup()
{
var script = new FairyLock();
var move = Substitute.For<IExecutingMove>();
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
var battle = Substitute.For<IBattle>();
battle.GetScripts().Returns(_ => new ScriptIterator(Array.Empty<IEnumerable<ScriptContainer>>()));
IScriptSet battleVolatile = new ScriptSet(battle);
battle.Volatile.Returns(battleVolatile);
var battleData = Substitute.For<IPokemonBattleData>();
battleData.Battle.Returns(battle);
target.BattleData.Returns(battleData);
target.Battle.Returns(battle);
return (script, move, target, battle, battleVolatile);
}
/// <summary>
/// Creates a mocked Pokémon with the given type names as its <see cref="IPokemon.Types"/>.
/// Creates a mocked Pokémon with the given type names as its <see cref="IBattlePokemon.Types"/>.
/// </summary>
private static IPokemon CreatePokemonWithTypes(params string[] types)
private static IBattlePokemon CreatePokemonWithTypes(params string[] types)
{
var pokemon = Substitute.For<IPokemon>();
var pokemon = Substitute.For<IBattlePokemon>();
pokemon.Types.Returns(types.Select((name, index) => new TypeIdentifier((byte)(index + 1), new StringKey(name)))
.ToList());
return pokemon;
@@ -68,23 +66,6 @@ public class FairyLockTests
await Assert.That(battleVolatile.Contains(ScriptUtils.ResolveName<FairyLockEffect>())).IsTrue();
}
/// <summary>
/// Technical test: without battle data on the target (outside of battle) the secondary effect does
/// nothing and does not throw.
/// </summary>
[Test]
public void OnSecondaryEffect_TargetHasNoBattleData_DoesNotThrow()
{
// Arrange
var script = new FairyLock();
var move = Substitute.For<IExecutingMove>();
var target = Substitute.For<IPokemon>();
target.BattleData.Returns((IPokemonBattleData?)null);
// Act & Assert - should not throw
script.OnSecondaryEffect(move, target, 0);
}
/// <summary>
/// Bulbapedia: "Fairy Lock prevents all Pokémon (except Ghost types) on the field from switching out".
/// A non-Ghost Pokémon is prevented from switching.

View File

@@ -25,13 +25,11 @@ public class FakeOutTests
var battle = Substitute.For<IBattle>();
battle.CurrentTurnNumber.Returns(currentTurn);
var battleData = Substitute.For<IPokemonBattleData>();
battleData.Battle.Returns(battle);
battleData.SwitchInTurn.Returns(switchInTurn);
var user = Substitute.For<IPokemon>();
user.BattleData.Returns(battleData);
var user = Substitute.For<IBattlePokemon>();
move.User.Returns(user);
user.SwitchInTurn.Returns(switchInTurn);
user.Battle.Returns(battle);
return (script, move);
}
@@ -74,28 +72,6 @@ public class FakeOutTests
await Assert.That(stop).IsTrue();
}
/// <summary>
/// Technical test: without battle data on the user (outside of battle) the first-turn check does nothing
/// and the move is not stopped.
/// </summary>
[Test]
public async Task StopBeforeMove_UserHasNoBattleData_MoveNotStopped()
{
// Arrange
var script = new FakeOut();
var move = Substitute.For<IExecutingMove>();
var user = Substitute.For<IPokemon>();
user.BattleData.Returns((IPokemonBattleData?)null);
move.User.Returns(user);
var stop = false;
// Act
script.StopBeforeMove(move, ref stop);
// Assert
await Assert.That(stop).IsFalse();
}
/// <summary>
/// Bulbapedia: "Fake Out inflicts damage and always makes the target flinch" — the secondary effect puts
/// a <see cref="FlinchEffect"/> on the target.
@@ -106,7 +82,7 @@ public class FakeOutTests
// Arrange
var script = new FakeOut();
var move = Substitute.For<IExecutingMove>();
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
target.GetScripts().Returns(_ => new ScriptIterator(Array.Empty<IEnumerable<ScriptContainer>>()));
IScriptSet volatileSet = new ScriptSet(target);
target.Volatile.Returns(volatileSet);

View File

@@ -14,11 +14,11 @@ public class FalseSwipeTests
/// <summary>
/// Creates a fully mocked test setup where the target has the given current health.
/// </summary>
private static (FalseSwipe script, IExecutingMove move, IPokemon target) CreateTestSetup(uint currentHealth)
private static (FalseSwipe script, IExecutingMove move, IBattlePokemon target) CreateTestSetup(uint currentHealth)
{
var script = new FalseSwipe();
var move = Substitute.For<IExecutingMove>();
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
target.CurrentHealth.Returns(currentHealth);
return (script, move, target);
}

View File

@@ -21,11 +21,11 @@ public class FeintTests
/// Creates a fully mocked test setup where the target has a real <see cref="ScriptSet"/> as its volatile
/// script set.
/// </summary>
private static (Feint script, IExecutingMove move, IPokemon target, IScriptSet volatileSet) CreateTestSetup()
private static (Feint script, IExecutingMove move, IBattlePokemon target, IScriptSet volatileSet) CreateTestSetup()
{
var script = new Feint();
var move = Substitute.For<IExecutingMove>();
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
target.GetScripts().Returns(_ => new ScriptIterator(Array.Empty<IEnumerable<ScriptContainer>>()));
IScriptSet volatileSet = new ScriptSet(target);
target.Volatile.Returns(volatileSet);
@@ -36,16 +36,14 @@ public class FeintTests
/// Extends the test setup with a battle side that has a real <see cref="ScriptSet"/> as its volatile
/// scripts, so side-wide protections such as <see cref="CraftyShieldEffect"/> can be attached.
/// </summary>
private static IScriptSet AttachSide(IPokemon target)
private static IScriptSet AttachSide(IBattlePokemon target)
{
var side = Substitute.For<IBattleSide>();
side.GetScripts().Returns(_ => new ScriptIterator(Array.Empty<IEnumerable<ScriptContainer>>()));
IScriptSet sideScripts = new ScriptSet(side);
side.VolatileScripts.Returns(sideScripts);
var battleData = Substitute.For<IPokemonBattleData>();
battleData.BattleSide.Returns(side);
target.BattleData.Returns(battleData);
target.BattleSide.Returns(side);
return sideScripts;
}

View File

@@ -14,14 +14,14 @@ public class FellStingerTests
/// <summary>
/// Creates a fully mocked test setup for Fell Stinger tests.
/// </summary>
private static (FellStinger script, IExecutingMove move, IPokemon user, IPokemon target) CreateTestSetup(
bool targetFainted)
private static (FellStinger script, IExecutingMove move, IBattlePokemon user, IBattlePokemon target)
CreateTestSetup(bool targetFainted)
{
var script = new FellStinger();
var move = Substitute.For<IExecutingMove>();
var user = Substitute.For<IPokemon>();
var user = Substitute.For<IBattlePokemon>();
move.User.Returns(user);
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
target.IsFainted.Returns(targetFainted);
return (script, move, user, target);
}
@@ -29,7 +29,7 @@ public class FellStingerTests
/// <summary>
/// Helper to find the first ChangeStatBoost call received by a Pokémon substitute.
/// </summary>
private static object[]? GetStatBoostArguments(IPokemon pokemon)
private static object[]? GetStatBoostArguments(IBattlePokemon pokemon)
{
var call = pokemon.ReceivedCalls().FirstOrDefault(c => c.GetMethodInfo().Name == "ChangeStatBoost");
return call?.GetArguments()!;

View File

@@ -13,22 +13,22 @@ public class FinalGambitTests
/// <summary>
/// Creates a fully mocked test setup for Final Gambit tests.
/// </summary>
private static (FinalGambit script, IExecutingMove move, IPokemon user, IPokemon target) CreateTestSetup(
uint userCurrentHealth)
private static (FinalGambit script, IExecutingMove move, IBattlePokemon user, IBattlePokemon target)
CreateTestSetup(uint userCurrentHealth)
{
var script = new FinalGambit();
var move = Substitute.For<IExecutingMove>();
var user = Substitute.For<IPokemon>();
var user = Substitute.For<IBattlePokemon>();
user.CurrentHealth.Returns(userCurrentHealth);
move.User.Returns(user);
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
return (script, move, user, target);
}
/// <summary>
/// Helper to extract the damage amount from the user's received Damage calls.
/// </summary>
private static uint? GetDamageAmount(IPokemon pokemon)
private static uint? GetDamageAmount(IBattlePokemon pokemon)
{
var call = pokemon.ReceivedCalls().FirstOrDefault(c => c.GetMethodInfo().Name == "Damage");
return call != null ? (uint)call.GetArguments()[0]! : null;
@@ -37,7 +37,7 @@ public class FinalGambitTests
/// <summary>
/// Helper to extract the damage source from the user's received Damage calls.
/// </summary>
private static DamageSource? GetDamageSource(IPokemon pokemon)
private static DamageSource? GetDamageSource(IBattlePokemon pokemon)
{
var call = pokemon.ReceivedCalls().FirstOrDefault(c => c.GetMethodInfo().Name == "Damage");
return call != null ? (DamageSource)call.GetArguments()[1]! : null;

View File

@@ -20,24 +20,22 @@ public class FireFangTests
/// Creates a fully mocked test setup for Fire Fang tests. The target has not yet moved this turn when
/// <paramref name="queue"/> contains a choice for it.
/// </summary>
private static (FireFang script, IExecutingMove move, IPokemon user, IPokemon target, IBattleRandom random,
IScriptSet targetVolatile) CreateTestSetup(BattleChoiceQueue? queue)
private static (FireFang script, IExecutingMove move, IBattlePokemon user, IBattlePokemon target, IBattleRandom
random, IScriptSet targetVolatile) CreateTestSetup(BattleChoiceQueue? queue)
{
var script = new FireFang();
var move = Substitute.For<IExecutingMove>();
var user = Substitute.For<IPokemon>();
var user = Substitute.For<IBattlePokemon>();
move.User.Returns(user);
var random = Substitute.For<IBattleRandom>();
var battle = Substitute.For<IBattle>();
battle.Random.Returns(random);
battle.ChoiceQueue.Returns(queue);
var battleData = Substitute.For<IPokemonBattleData>();
battleData.Battle.Returns(battle);
var target = Substitute.For<IPokemon>();
target.BattleData.Returns(battleData);
var target = Substitute.For<IBattlePokemon>();
var targetVolatile = Substitute.For<IScriptSet>();
target.Battle.Returns(battle);
target.Volatile.Returns(targetVolatile);
return (script, move, user, target, random, targetVolatile);
@@ -46,7 +44,7 @@ public class FireFangTests
/// <summary>
/// Creates a choice queue containing a single yet-to-execute move choice for the given Pokémon.
/// </summary>
private static BattleChoiceQueue CreateQueueWithChoiceFor(IPokemon pokemon)
private static BattleChoiceQueue CreateQueueWithChoiceFor(IBattlePokemon pokemon)
{
var choice = Substitute.For<IMoveChoice>();
choice.User.Returns(pokemon);
@@ -58,7 +56,7 @@ public class FireFangTests
/// matchers cannot be used for <see cref="EventBatchId"/> parameters: its parameterless constructor
/// generates a random Guid, which breaks NSubstitute's argument specification binding.)
/// </summary>
private static bool ReceivedSetStatus(IPokemon pokemon) =>
private static bool ReceivedSetStatus(IBattlePokemon pokemon) =>
pokemon.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "SetStatus");
/// <summary>
@@ -126,7 +124,7 @@ public class FireFangTests
// Arrange
var (script, move, _, target, random, targetVolatile) = CreateTestSetup(null);
var queue = CreateQueueWithChoiceFor(target);
target.BattleData!.Battle.ChoiceQueue.Returns(queue);
target.Battle.ChoiceQueue.Returns(queue);
random.EffectChance(10, move, target, 0).Returns(true, true);
// Act
@@ -147,7 +145,7 @@ public class FireFangTests
// Arrange
var (script, move, _, target, random, targetVolatile) = CreateTestSetup(null);
var queue = CreateQueueWithChoiceFor(target);
target.BattleData!.Battle.ChoiceQueue.Returns(queue);
target.Battle.ChoiceQueue.Returns(queue);
// First roll (burn) fails, second roll (flinch) succeeds.
random.EffectChance(10, move, target, 0).Returns(false, true);
@@ -170,8 +168,8 @@ public class FireFangTests
// Arrange
var (script, move, _, target, random, targetVolatile) = CreateTestSetup(null);
// The queue only holds a choice for some other Pokémon; the target's choice already executed.
var queue = CreateQueueWithChoiceFor(Substitute.For<IPokemon>());
target.BattleData!.Battle.ChoiceQueue.Returns(queue);
var queue = CreateQueueWithChoiceFor(Substitute.For<IBattlePokemon>());
target.Battle.ChoiceQueue.Returns(queue);
random.EffectChance(10, move, target, 0).Returns(true, true);
// Act
@@ -199,26 +197,4 @@ public class FireFangTests
target.Received(1).SetStatus("burned", user);
targetVolatile.DidNotReceive().Add(Arg.Any<Script>());
}
/// <summary>
/// Technical test: if the target has no <see cref="IPokemon.BattleData"/>, the script does nothing.
/// </summary>
[Test]
public async Task OnSecondaryEffect_TargetHasNoBattleData_DoesNothing()
{
// Arrange
var script = new FireFang();
var move = Substitute.For<IExecutingMove>();
var target = Substitute.For<IPokemon>();
target.BattleData.Returns((IPokemonBattleData?)null);
var targetVolatile = Substitute.For<IScriptSet>();
target.Volatile.Returns(targetVolatile);
// Act
script.OnSecondaryEffect(move, target, 0);
// Assert
await Assert.That(ReceivedSetStatus(target)).IsFalse();
targetVolatile.DidNotReceive().Add(Arg.Any<Script>());
}
}

View File

@@ -26,10 +26,8 @@ public class FirePledgeTests
var choice = Substitute.For<IMoveChoice>();
choice.GetScripts().Returns(_ => new ScriptIterator(new List<IEnumerable<ScriptContainer>>()));
var pokemon = Substitute.For<IPokemon>();
var battleData = Substitute.For<IPokemonBattleData>();
battleData.SideIndex.Returns(sideIndex);
pokemon.BattleData.Returns(battleData);
var pokemon = Substitute.For<IBattlePokemon>();
pokemon.SideIndex.Returns(sideIndex);
choice.User.Returns(pokemon);
var moveData = Substitute.For<IMoveData>();
@@ -56,10 +54,8 @@ public class FirePledgeTests
battle.ChoiceQueue.Returns(queue);
move.Battle.Returns(battle);
var user = Substitute.For<IPokemon>();
var userBattleData = Substitute.For<IPokemonBattleData>();
userBattleData.SideIndex.Returns((byte)0);
user.BattleData.Returns(userBattleData);
var user = Substitute.For<IBattlePokemon>();
user.SideIndex.Returns((byte)0);
move.User.Returns(user);
var ownChoice = CreateQueuedChoice("fire_pledge", 0);

View File

@@ -22,11 +22,12 @@ public class FireSpinTests
/// <see cref="ScriptSet"/> as its volatile set, so the applied effect can be inspected.
/// The battle random is stubbed to return 5, the maximum of Fire Spin's four-to-five turn duration.
/// </summary>
private static (FireSpin script, IExecutingMove move, IPokemon target, IScriptSet targetVolatile) CreateTestSetup()
private static (FireSpin script, IExecutingMove move, IBattlePokemon target, IScriptSet targetVolatile)
CreateTestSetup()
{
var script = new FireSpin();
var move = Substitute.For<IExecutingMove>();
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
target.GetScripts().Returns(_ => new ScriptIterator(new List<IEnumerable<ScriptContainer>>()));
var targetVolatile = new ScriptSet(target);
target.Volatile.Returns(targetVolatile);
@@ -35,9 +36,7 @@ public class FireSpinTests
random.GetInt(Arg.Any<int>(), Arg.Any<int>()).Returns(5);
var battle = Substitute.For<IBattle>();
battle.Random.Returns(random);
var battleData = Substitute.For<IPokemonBattleData>();
battleData.Battle.Returns(battle);
target.BattleData.Returns(battleData);
target.Battle.Returns(battle);
return (script, move, target, targetVolatile);
}
@@ -84,19 +83,20 @@ public class FireSpinTests
/// Creates a trapped Pokémon substitute with the given maximum HP, together with the
/// <see cref="FireSpinEffect"/> that traps it.
/// </summary>
private static (FireSpinEffect effect, IPokemon owner) CreateTrappedPokemon(uint maxHealth, IPokemon? user = null)
private static (FireSpinEffect effect, IBattlePokemon owner) CreateTrappedPokemon(uint maxHealth,
IBattlePokemon? user = null)
{
var owner = Substitute.For<IPokemon>();
var owner = Substitute.For<IBattlePokemon>();
owner.BoostedStats.Returns(new StatisticSet<uint>(maxHealth, 1, 1, 1, 1, 1));
owner.MaxHealth.Returns(maxHealth);
owner.Types.Returns(new List<TypeIdentifier> { new(10, "fire") });
return (new FireSpinEffect(owner, 5, user ?? Substitute.For<IPokemon>()), owner);
return (new FireSpinEffect(owner, 5, user ?? Substitute.For<IBattlePokemon>()), owner);
}
/// <summary>
/// Helper to extract the damage amount from a Pokémon's received Damage calls.
/// </summary>
private static uint? GetDamageAmount(IPokemon pokemon)
private static uint? GetDamageAmount(IBattlePokemon pokemon)
{
var call = pokemon.ReceivedCalls().FirstOrDefault(c => c.GetMethodInfo().Name == "Damage");
return call != null ? (uint)call.GetArguments()[0]! : null;
@@ -206,7 +206,7 @@ public class FireSpinTests
{
// Arrange
var (script, move, target, targetVolatile) = CreateTestSetup();
var user = Substitute.For<IPokemon>();
var user = Substitute.For<IBattlePokemon>();
user.HasHeldItem("binding_band").Returns(true);
move.User.Returns(user);
target.BoostedStats.Returns(new StatisticSet<uint>(120, 1, 1, 1, 1, 1));

View File

@@ -15,19 +15,17 @@ public class FirstImpressionTests
/// Creates a fully mocked test setup for First Impression tests, with the user having switched in on
/// <paramref name="switchInTurn"/> while the battle is on <paramref name="currentTurn"/>.
/// </summary>
private static (FirstImpression script, IExecutingMove move, IPokemon user) CreateTestSetup(uint switchInTurn,
private static (FirstImpression script, IExecutingMove move, IBattlePokemon user) CreateTestSetup(uint switchInTurn,
uint currentTurn)
{
var script = new FirstImpression();
var move = Substitute.For<IExecutingMove>();
var user = Substitute.For<IPokemon>();
var user = Substitute.For<IBattlePokemon>();
var battle = Substitute.For<IBattle>();
battle.CurrentTurnNumber.Returns(currentTurn);
var battleData = Substitute.For<IPokemonBattleData>();
battleData.Battle.Returns(battle);
battleData.SwitchInTurn.Returns(switchInTurn);
user.BattleData.Returns(battleData);
user.Battle.Returns(battle);
user.SwitchInTurn.Returns(switchInTurn);
move.User.Returns(user);
return (script, move, user);
@@ -88,26 +86,4 @@ public class FirstImpressionTests
// Assert
await Assert.That(stop).IsEqualTo(expectedStop);
}
/// <summary>
/// Technical test: if the user has no <see cref="IPokemon.BattleData"/>, the script cannot determine the
/// switch-in turn and leaves the move untouched.
/// </summary>
[Test]
public async Task StopBeforeMove_NoBattleData_MoveIsNotStopped()
{
// Arrange
var script = new FirstImpression();
var move = Substitute.For<IExecutingMove>();
var user = Substitute.For<IPokemon>();
user.BattleData.Returns((IPokemonBattleData?)null);
move.User.Returns(user);
var stop = false;
// Act
script.StopBeforeMove(move, ref stop);
// Assert
await Assert.That(stop).IsFalse();
}
}

View File

@@ -17,12 +17,13 @@ public class FlailTests
/// <summary>
/// Creates a fully mocked test setup for Flail tests with the user at the given current and maximum HP.
/// </summary>
private static (Flail script, IExecutingMove move, IPokemon target) CreateTestSetup(uint currentHp, uint maxHp)
private static (Flail script, IExecutingMove move, IBattlePokemon target) CreateTestSetup(uint currentHp,
uint maxHp)
{
var script = new Flail();
var move = Substitute.For<IExecutingMove>();
var target = Substitute.For<IPokemon>();
var user = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
var user = Substitute.For<IBattlePokemon>();
user.CurrentHealth.Returns(currentHp);
user.BoostedStats.Returns(new StatisticSet<uint>(maxHp, 0, 0, 0, 0, 0));
move.User.Returns(user);

View File

@@ -15,14 +15,12 @@ public class FlameBurstTests
/// <summary>
/// Creates a mocked battle Pokémon that lives in <paramref name="battle"/> on the given side and position.
/// </summary>
private static IPokemon CreateBattlePokemon(IBattle battle, byte sideIndex, byte position, uint maxHp = 100)
private static IBattlePokemon CreateBattlePokemon(IBattle battle, byte sideIndex, byte position, uint maxHp = 100)
{
var pokemon = Substitute.For<IPokemon>();
var battleData = Substitute.For<IPokemonBattleData>();
battleData.Battle.Returns(battle);
battleData.SideIndex.Returns(sideIndex);
battleData.Position.Returns(position);
pokemon.BattleData.Returns(battleData);
var pokemon = Substitute.For<IBattlePokemon>();
pokemon.Battle.Returns(battle);
pokemon.SideIndex.Returns(sideIndex);
pokemon.Position.Returns(position);
pokemon.BoostedStats.Returns(new StatisticSet<uint>(maxHp, 0, 0, 0, 0, 0));
pokemon.MaxHealth.Returns(maxHp);
return pokemon;
@@ -31,7 +29,7 @@ public class FlameBurstTests
/// <summary>
/// Configures the two sides of <paramref name="battle"/> with the given Pokémon lists.
/// </summary>
private static void SetSides(IBattle battle, List<IPokemon?> side0Pokemon, List<IPokemon?> side1Pokemon)
private static void SetSides(IBattle battle, List<IBattlePokemon?> side0Pokemon, List<IBattlePokemon?> side1Pokemon)
{
var side0 = Substitute.For<IBattleSide>();
side0.Pokemon.Returns(side0Pokemon);
@@ -44,8 +42,8 @@ public class FlameBurstTests
/// Creates a fully mocked double battle: the user and its ally on side 0, the target and its ally on
/// side 1. Flame Burst is used by the user against the target.
/// </summary>
private static (FlameBurst script, IExecutingMove move, IPokemon user, IPokemon userAlly, IPokemon target, IPokemon
targetAlly) CreateDoubleBattleSetup(uint targetAllyMaxHp = 160)
private static (FlameBurst script, IExecutingMove move, IBattlePokemon user, IBattlePokemon userAlly, IBattlePokemon
target, IBattlePokemon targetAlly) CreateDoubleBattleSetup(uint targetAllyMaxHp = 160)
{
var script = new FlameBurst();
var battle = Substitute.For<IBattle>();
@@ -66,7 +64,7 @@ public class FlameBurstTests
/// <summary>
/// Helper to extract the damage amount from a Pokémon's received Damage calls, or 0 if none was received.
/// </summary>
private static uint GetDamageAmount(IPokemon pokemon)
private static uint GetDamageAmount(IBattlePokemon pokemon)
{
var call = pokemon.ReceivedCalls().FirstOrDefault(c => c.GetMethodInfo().Name == "Damage");
return call != null ? (uint)call.GetArguments()[0]! : 0;
@@ -75,7 +73,7 @@ public class FlameBurstTests
/// <summary>
/// Helper to extract the damage source from a Pokémon's received Damage calls.
/// </summary>
private static DamageSource? GetDamageSource(IPokemon pokemon)
private static DamageSource? GetDamageSource(IBattlePokemon pokemon)
{
var call = pokemon.ReceivedCalls().FirstOrDefault(c => c.GetMethodInfo().Name == "Damage");
return call != null ? (DamageSource)call.GetArguments()[1]! : null;
@@ -84,7 +82,7 @@ public class FlameBurstTests
/// <summary>
/// Helper that checks whether a Pokémon received any Damage call.
/// </summary>
private static bool WasDamaged(IPokemon pokemon) =>
private static bool WasDamaged(IBattlePokemon pokemon) =>
pokemon.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "Damage");
/// <summary>
@@ -266,23 +264,4 @@ public class FlameBurstTests
// Act & Assert
await Assert.That(() => script.OnSecondaryEffect(move, target, 0)).ThrowsNothing();
}
/// <summary>
/// Technical test: when no battle data is available the script does nothing instead of throwing.
/// </summary>
[Test]
public async Task OnSecondaryEffect_NoBattleData_DoesNotThrow()
{
// Arrange
var script = new FlameBurst();
var move = Substitute.For<IExecutingMove>();
var user = Substitute.For<IPokemon>();
user.BattleData.Returns((IPokemonBattleData?)null);
move.User.Returns(user);
var target = Substitute.For<IPokemon>();
target.BattleData.Returns((IPokemonBattleData?)null);
// Act & Assert
await Assert.That(() => script.OnSecondaryEffect(move, target, 0)).ThrowsNothing();
}
}

View File

@@ -14,15 +14,15 @@ public class FlameWheelTests
/// <summary>
/// Creates a fully mocked test setup for Flame Wheel tests, initialized with the given burn chance.
/// </summary>
private static (FlameWheel script, IExecutingMove move, IPokemon user, IPokemon target, IBattleRandom random)
CreateTestSetup(float burnChance = 10f)
private static (FlameWheel script, IExecutingMove move, IBattlePokemon user, IBattlePokemon target, IBattleRandom
random) CreateTestSetup(float burnChance = 10f)
{
var script = new FlameWheel();
script.OnInitialize(new Dictionary<StringKey, object?> { { "burn_chance", burnChance } });
var move = Substitute.For<IExecutingMove>();
var user = Substitute.For<IPokemon>();
var target = Substitute.For<IPokemon>();
var user = Substitute.For<IBattlePokemon>();
var target = Substitute.For<IBattlePokemon>();
var battle = Substitute.For<IBattle>();
var random = Substitute.For<IBattleRandom>();
@@ -36,14 +36,14 @@ public class FlameWheelTests
/// <summary>
/// Helper that checks whether a Pokémon received a SetStatus call for the given status.
/// </summary>
private static bool ReceivedSetStatus(IPokemon pokemon, string status) =>
private static bool ReceivedSetStatus(IBattlePokemon pokemon, string status) =>
pokemon.ReceivedCalls().Any(c =>
c.GetMethodInfo().Name == "SetStatus" && (StringKey)c.GetArguments()[0]! == new StringKey(status));
/// <summary>
/// Helper that checks whether a Pokémon received a ClearStatus call.
/// </summary>
private static bool ReceivedClearStatus(IPokemon pokemon) =>
private static bool ReceivedClearStatus(IBattlePokemon pokemon) =>
pokemon.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "ClearStatus");
/// <summary>

View File

@@ -17,13 +17,13 @@ public class FlareBlitzTests
/// Creates a fully mocked test setup for Flare Blitz tests, where the hit dealt
/// <paramref name="damage"/> damage to the target.
/// </summary>
private static (FlareBlitz script, IExecutingMove move, IPokemon user, IPokemon target, IBattleRandom random)
CreateTestSetup(uint damage, Script[]? moveScripts = null)
private static (FlareBlitz script, IExecutingMove move, IBattlePokemon user, IBattlePokemon target, IBattleRandom
random) CreateTestSetup(uint damage, Script[]? moveScripts = null)
{
var script = new FlareBlitz();
var move = Substitute.For<IExecutingMove>();
var user = Substitute.For<IPokemon>();
var target = Substitute.For<IPokemon>();
var user = Substitute.For<IBattlePokemon>();
var target = Substitute.For<IBattlePokemon>();
var hitData = Substitute.For<IHitData>();
hitData.Damage.Returns(damage);
@@ -32,9 +32,7 @@ public class FlareBlitzTests
var battle = Substitute.For<IBattle>();
var random = Substitute.For<IBattleRandom>();
battle.Random.Returns(random);
var battleData = Substitute.For<IPokemonBattleData>();
battleData.Battle.Returns(battle);
target.BattleData.Returns(battleData);
target.Battle.Returns(battle);
// RunScriptHook iterates the move's scripts; give the mock a real iterator so the hook pass runs
// (empty unless the test attaches scripts such as Rock Head).
@@ -50,7 +48,7 @@ public class FlareBlitzTests
/// <summary>
/// Helper to extract the damage amount from a Pokémon's received Damage calls, or 0 if none was received.
/// </summary>
private static uint GetDamageAmount(IPokemon pokemon)
private static uint GetDamageAmount(IBattlePokemon pokemon)
{
var call = pokemon.ReceivedCalls().FirstOrDefault(c => c.GetMethodInfo().Name == "Damage");
return call != null ? (uint)call.GetArguments()[0]! : 0;
@@ -59,7 +57,7 @@ public class FlareBlitzTests
/// <summary>
/// Helper to extract the damage source from a Pokémon's received Damage calls.
/// </summary>
private static DamageSource? GetDamageSource(IPokemon pokemon)
private static DamageSource? GetDamageSource(IBattlePokemon pokemon)
{
var call = pokemon.ReceivedCalls().FirstOrDefault(c => c.GetMethodInfo().Name == "Damage");
return call != null ? (DamageSource)call.GetArguments()[1]! : null;
@@ -68,7 +66,7 @@ public class FlareBlitzTests
/// <summary>
/// Helper that checks whether a Pokémon received a SetStatus call for the given status.
/// </summary>
private static bool ReceivedSetStatus(IPokemon pokemon, string status) =>
private static bool ReceivedSetStatus(IBattlePokemon pokemon, string status) =>
pokemon.ReceivedCalls().Any(c =>
c.GetMethodInfo().Name == "SetStatus" && (StringKey)c.GetArguments()[0]! == new StringKey(status));
@@ -212,23 +210,4 @@ public class FlareBlitzTests
// Assert
await Assert.That(ReceivedSetStatus(target, "burned")).IsTrue();
}
/// <summary>
/// Technical test: when the target has no <see cref="IPokemon.BattleData"/>, the script does nothing;
/// no recoil is taken and no burn is rolled.
/// </summary>
[Test]
public async Task OnSecondaryEffect_TargetHasNoBattleData_NoRecoilOrBurn()
{
// Arrange
var (script, move, user, target, _) = CreateTestSetup(90);
target.BattleData.Returns((IPokemonBattleData?)null);
// Act
script.OnSecondaryEffect(move, target, 0);
// Assert
await Assert.That(user.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "Damage")).IsFalse();
await Assert.That(ReceivedSetStatus(target, "burned")).IsFalse();
}
}

View File

@@ -14,14 +14,15 @@ namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves;
/// </summary>
public class FlatterTests
{
private static (Flatter script, IExecutingMove move, IPokemon target, IScriptSet targetVolatile) CreateTestSetup()
private static (Flatter script, IExecutingMove move, IBattlePokemon target, IScriptSet targetVolatile)
CreateTestSetup()
{
var script = new Flatter();
var move = Substitute.For<IExecutingMove>();
var user = Substitute.For<IPokemon>();
var user = Substitute.For<IBattlePokemon>();
move.User.Returns(user);
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
var targetVolatile = Substitute.For<IScriptSet>();
target.Volatile.Returns(targetVolatile);
@@ -34,7 +35,7 @@ public class FlatterTests
/// 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)
private static object?[]? GetStatBoostArgs(IBattlePokemon pokemon)
{
var call = pokemon.ReceivedCalls().FirstOrDefault(c => c.GetMethodInfo().Name == "ChangeStatBoost");
return call?.GetArguments();
@@ -64,7 +65,7 @@ public class FlatterTests
/// <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.
/// through the target's <see cref="IBattlePokemon.Volatile"/> script set.
/// </summary>
[Test]
public async Task OnSecondaryEffect_Always_ConfusesTarget()
@@ -101,7 +102,7 @@ public class FlatterTests
/// <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
/// confused." A substitute's <see cref="IBattlePokemon.ChangeStatBoost"/> returns false by default (the
/// boost failed), yet the confusion is still applied.
/// </summary>
[Test]

View File

@@ -12,16 +12,16 @@ namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves;
/// </summary>
public class FlingTests
{
private static (Fling script, IExecutingMove move, IPokemon target, IPokemon user, IHitData hitData)
private static (Fling script, IExecutingMove move, IBattlePokemon target, IBattlePokemon user, IHitData hitData)
CreateTestSetup(IItem? heldItem)
{
var script = new Fling();
var move = Substitute.For<IExecutingMove>();
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
var hitData = Substitute.For<IHitData>();
move.GetHitData(target, 0).Returns(hitData);
var user = Substitute.For<IPokemon>();
var user = Substitute.For<IBattlePokemon>();
user.HeldItem.Returns(heldItem);
move.User.Returns(user);
@@ -123,7 +123,7 @@ public class FlingTests
/// <summary>
/// Bulbapedia: "After using Fling, the item is consumed". The user's held item is removed for the
/// remainder of the battle via <see cref="IPokemon.RemoveHeldItemForBattle"/>.
/// remainder of the battle via <see cref="IBattlePokemon.RemoveHeldItemForBattle"/>.
/// </summary>
[Test]
public async Task OnSecondaryEffect_Always_RemovesHeldItemForBattle()

View File

@@ -12,8 +12,8 @@ namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves;
/// </summary>
public class FloralHealingTests
{
private static (FloralHealing script, IExecutingMove move, IPokemon target) CreateTestSetup(uint maxHp,
string? terrainName = null, bool isFainted = false, bool hasBattleData = true)
private static (FloralHealing script, IExecutingMove move, IBattlePokemon target) CreateTestSetup(uint maxHp,
string? terrainName = null, bool isFainted = false)
{
var script = new FloralHealing();
var move = Substitute.For<IExecutingMove>();
@@ -21,19 +21,10 @@ public class FloralHealingTests
var battle = Substitute.For<IBattle>();
battle.TerrainName.Returns(terrainName == null ? null : new StringKey?(new StringKey(terrainName)));
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
target.IsFainted.Returns(isFainted);
target.BoostedStats.Returns(new StatisticSet<uint>(maxHp, 0, 0, 0, 0, 0));
if (hasBattleData)
{
var battleData = Substitute.For<IPokemonBattleData>();
battleData.Battle.Returns(battle);
target.BattleData.Returns(battleData);
}
else
{
target.BattleData.Returns((IPokemonBattleData?)null);
}
target.Battle.Returns(battle);
return (script, move, target);
}
@@ -41,7 +32,7 @@ public class FloralHealingTests
/// <summary>
/// Helper to extract the heal amount from the target's received Heal calls.
/// </summary>
private static uint? GetHealAmount(IPokemon target)
private static uint? GetHealAmount(IBattlePokemon target)
{
var call = target.ReceivedCalls().FirstOrDefault(c => c.GetMethodInfo().Name == "Heal");
return call != null ? (uint)call.GetArguments()[0]! : null;
@@ -116,7 +107,7 @@ public class FloralHealingTests
/// <summary>
/// Bulbapedia: Floral Healing "restores" HP of the target; a fainted Pokémon can no longer be healed,
/// so no heal is applied when the target <see cref="IPokemon.IsFainted"/>.
/// so no heal is applied when the target <see cref="IBattlePokemon.IsFainted"/>.
/// </summary>
[Test]
public async Task OnSecondaryEffect_TargetFainted_DoesNotHeal()
@@ -130,21 +121,4 @@ public class FloralHealingTests
// Assert
await Assert.That(target.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "Heal")).IsFalse();
}
/// <summary>
/// Technical test: when the target has no <see cref="IPokemon.BattleData"/> (it is not in a battle),
/// the script does nothing instead of throwing.
/// </summary>
[Test]
public async Task OnSecondaryEffect_NullBattleData_DoesNotHeal()
{
// Arrange
var (script, move, target) = CreateTestSetup(100, hasBattleData: false);
// Act
script.OnSecondaryEffect(move, target, 0);
// Assert
await Assert.That(target.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "Heal")).IsFalse();
}
}

View File

@@ -24,12 +24,12 @@ public class FlowerShieldTests
library.StaticLibrary.Types.TryGetTypeIdentifier("water", out WaterType);
}
private static (FlowerShield script, IExecutingMove move, IPokemon target, IBattle battle) CreateTestSetup(
params IReadOnlyList<IPokemon?>[] sidePokemon)
private static (FlowerShield script, IExecutingMove move, IBattlePokemon target, IBattle battle) CreateTestSetup(
params IReadOnlyList<IBattlePokemon?>[] sidePokemon)
{
var script = new FlowerShield();
var move = Substitute.For<IExecutingMove>();
var user = Substitute.For<IPokemon>();
var user = Substitute.For<IBattlePokemon>();
move.User.Returns(user);
var library = LibraryHelpers.LoadLibrary();
@@ -43,23 +43,21 @@ public class FlowerShieldTests
}).ToArray();
battle.Sides.Returns(sides);
var battleData = Substitute.For<IPokemonBattleData>();
battleData.Battle.Returns(battle);
var target = Substitute.For<IPokemon>();
target.BattleData.Returns(battleData);
var target = Substitute.For<IBattlePokemon>();
target.Battle.Returns(battle);
return (script, move, target, battle);
}
private static IPokemon CreatePokemon(TypeIdentifier type, bool isFainted = false)
private static IBattlePokemon CreatePokemon(TypeIdentifier type, bool isFainted = false)
{
var pokemon = Substitute.For<IPokemon>();
var pokemon = Substitute.For<IBattlePokemon>();
pokemon.Types.Returns(new[] { type });
pokemon.IsFainted.Returns(isFainted);
return pokemon;
}
private static bool ReceivedStatBoost(IPokemon pokemon) =>
private static bool ReceivedStatBoost(IBattlePokemon pokemon) =>
pokemon.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "ChangeStatBoost");
/// <summary>
@@ -68,7 +66,7 @@ public class FlowerShieldTests
/// 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)
private static object?[]? GetStatBoostArgs(IBattlePokemon pokemon)
{
var call = pokemon.ReceivedCalls().FirstOrDefault(c => c.GetMethodInfo().Name == "ChangeStatBoost");
return call?.GetArguments();
@@ -83,7 +81,7 @@ public class FlowerShieldTests
{
// Arrange
var grassPokemon = CreatePokemon(GrassType);
var (script, move, target, _) = CreateTestSetup(new List<IPokemon?> { grassPokemon });
var (script, move, target, _) = CreateTestSetup(new List<IBattlePokemon?> { grassPokemon });
// Act
script.OnSecondaryEffect(move, target, 0);
@@ -107,8 +105,8 @@ public class FlowerShieldTests
// Arrange
var allyGrass = CreatePokemon(GrassType);
var opposingGrass = CreatePokemon(GrassType);
var (script, move, target, _) = CreateTestSetup(new List<IPokemon?> { allyGrass },
new List<IPokemon?> { opposingGrass });
var (script, move, target, _) = CreateTestSetup(new List<IBattlePokemon?> { allyGrass },
new List<IBattlePokemon?> { opposingGrass });
// Act
script.OnSecondaryEffect(move, target, 0);
@@ -127,7 +125,7 @@ public class FlowerShieldTests
{
// Arrange
var waterPokemon = CreatePokemon(WaterType);
var (script, move, target, _) = CreateTestSetup(new List<IPokemon?> { waterPokemon });
var (script, move, target, _) = CreateTestSetup(new List<IBattlePokemon?> { waterPokemon });
// Act
script.OnSecondaryEffect(move, target, 0);
@@ -145,7 +143,7 @@ public class FlowerShieldTests
{
// Arrange
var faintedGrass = CreatePokemon(GrassType, true);
var (script, move, target, _) = CreateTestSetup(new List<IPokemon?> { faintedGrass });
var (script, move, target, _) = CreateTestSetup(new List<IBattlePokemon?> { faintedGrass });
// Act
script.OnSecondaryEffect(move, target, 0);
@@ -167,7 +165,7 @@ public class FlowerShieldTests
user.Types.Returns(new[] { GrassType });
user.IsFainted.Returns(false);
var side = Substitute.For<IBattleSide>();
side.Pokemon.Returns(new List<IPokemon?> { user });
side.Pokemon.Returns(new List<IBattlePokemon?> { user });
battle.Sides.Returns(new[] { side });
// Act
@@ -195,7 +193,7 @@ public class FlowerShieldTests
var flyingGrassVolatile = new ScriptSet(flyingGrass);
flyingGrassVolatile.Add(new ChargeFlyEffect(flyingGrass));
flyingGrass.Volatile.Returns(flyingGrassVolatile);
var (script, move, target, _) = CreateTestSetup(new List<IPokemon?> { flyingGrass });
var (script, move, target, _) = CreateTestSetup(new List<IBattlePokemon?> { flyingGrass });
// Act
script.OnSecondaryEffect(move, target, 0);
@@ -213,7 +211,7 @@ public class FlowerShieldTests
{
// Arrange
var waterPokemon = CreatePokemon(WaterType);
var (script, move, target, _) = CreateTestSetup(new List<IPokemon?> { waterPokemon });
var (script, move, target, _) = CreateTestSetup(new List<IBattlePokemon?> { waterPokemon });
var hitData = Substitute.For<IHitData>();
move.GetHitData(target, 0).Returns(hitData);
@@ -232,7 +230,7 @@ public class FlowerShieldTests
{
// Arrange
var grassPokemon = CreatePokemon(GrassType);
var (script, move, target, _) = CreateTestSetup(new List<IPokemon?> { null, grassPokemon });
var (script, move, target, _) = CreateTestSetup(new List<IBattlePokemon?> { null, grassPokemon });
// Act
script.OnSecondaryEffect(move, target, 0);
@@ -240,24 +238,4 @@ public class FlowerShieldTests
// Assert
await Assert.That(ReceivedStatBoost(grassPokemon)).IsTrue();
}
/// <summary>
/// Technical test: when the target has no <see cref="IPokemon.BattleData"/> (it is not in a battle),
/// the script does nothing instead of throwing.
/// </summary>
[Test]
public async Task OnSecondaryEffect_NullBattleData_DoesNothing()
{
// Arrange
var grassPokemon = CreatePokemon(GrassType);
var (script, move, _, _) = CreateTestSetup(new List<IPokemon?> { grassPokemon });
var target = Substitute.For<IPokemon>();
target.BattleData.Returns((IPokemonBattleData?)null);
// Act
script.OnSecondaryEffect(move, target, 0);
// Assert
await Assert.That(ReceivedStatBoost(grassPokemon)).IsFalse();
}
}

View File

@@ -20,12 +20,12 @@ namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves;
/// </summary>
public class FlyTests
{
private static (Fly script, IExecutingMove move, IPokemon user, ScriptSet userVolatile, IMoveChoice moveChoice,
EventHook eventHook) CreateTestSetup()
private static (Fly script, IExecutingMove move, IBattlePokemon user, ScriptSet userVolatile, IMoveChoice moveChoice
, EventHook eventHook) CreateTestSetup()
{
var script = new Fly();
var move = Substitute.For<IExecutingMove>();
var user = Substitute.For<IPokemon>();
var user = Substitute.For<IBattlePokemon>();
// Use a real script set so the charge volatile added by Fly can be inspected afterwards.
var userVolatile = new ScriptSet(user);
user.Volatile.Returns(userVolatile);
@@ -38,9 +38,7 @@ public class FlyTests
var eventHook = new EventHook();
var battle = Substitute.For<IBattle>();
battle.EventHook.Returns(eventHook);
var battleData = Substitute.For<IPokemonBattleData>();
battleData.Battle.Returns(battle);
user.BattleData.Returns(battleData);
user.Battle.Returns(battle);
return (script, move, user, userVolatile, moveChoice, eventHook);
}
@@ -146,33 +144,6 @@ public class FlyTests
await Assert.That(prevent).IsFalse();
}
/// <summary>
/// Technical test: a user that is not in a battle (no <see cref="IPokemon.BattleData"/>) still starts
/// the charge turn without throwing; only the dialog event is skipped.
/// </summary>
[Test]
public async Task PreventMove_NoBattleData_StillPreventsMove()
{
// Arrange
var script = new Fly();
var move = Substitute.For<IExecutingMove>();
var user = Substitute.For<IPokemon>();
var userVolatile = new ScriptSet(user);
user.Volatile.Returns(userVolatile);
user.GetScripts().Returns(_ => new ScriptIterator(new List<IEnumerable<ScriptContainer>>()));
user.BattleData.Returns((IPokemonBattleData?)null);
move.User.Returns(user);
move.MoveChoice.Returns(Substitute.For<IMoveChoice>());
var prevent = false;
// Act
script.PreventMove(move, ref prevent);
// Assert
await Assert.That(prevent).IsTrue();
await Assert.That(userVolatile.Contains(ScriptUtils.ResolveName<ChargeFlyEffect>())).IsTrue();
}
/// <summary>
/// Bulbapedia: the attack executes on the second turn, ending the semi-invulnerable state. Once the
/// attack executes, the <see cref="ChargeFlyEffect"/> is removed from the user.
@@ -309,7 +280,7 @@ public class FlyTests
public async Task OnAfterMoveChoice_ChoiceIsNotFlyCharge_RemovesChargeFlyEffect()
{
// Arrange
var user = Substitute.For<IPokemon>();
var user = Substitute.For<IBattlePokemon>();
var userVolatile = new ScriptSet(user);
user.GetScripts().Returns(_ => new ScriptIterator(new List<IEnumerable<ScriptContainer>>()));
var effect = new ChargeFlyEffect(user);
@@ -332,7 +303,7 @@ public class FlyTests
public async Task OnAfterMoveChoice_ChoiceIsFlyCharge_KeepsChargeFlyEffect()
{
// Arrange
var user = Substitute.For<IPokemon>();
var user = Substitute.For<IBattlePokemon>();
var userVolatile = new ScriptSet(user);
user.GetScripts().Returns(_ => new ScriptIterator(new List<IEnumerable<ScriptContainer>>()));
var effect = new ChargeFlyEffect(user);

View File

@@ -15,7 +15,7 @@ public class FlyingPressTests
/// Creates a fully mocked test setup for Flying Press tests, with a type library containing both the
/// Fighting and Flying types.
/// </summary>
private static (FlyingPress flyingPress, IExecutingMove move, IPokemon target, TypeIdentifier fighting,
private static (FlyingPress flyingPress, IExecutingMove move, IBattlePokemon target, TypeIdentifier fighting,
TypeIdentifier flying) CreateTestSetup()
{
var flyingPress = new FlyingPress();
@@ -25,7 +25,7 @@ public class FlyingPressTests
var move = Substitute.For<IExecutingMove>();
move.User.Library.StaticLibrary.Types.Returns(typeLibrary);
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
return (flyingPress, move, target, fighting, flying);
}
@@ -83,7 +83,7 @@ public class FlyingPressTests
var move = Substitute.For<IExecutingMove>();
move.User.Library.StaticLibrary.Types.Returns(typeLibrary);
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
IList<TypeIdentifier> types = new List<TypeIdentifier> { fighting };
// Act

View File

@@ -18,12 +18,12 @@ public class FocusEnergyTests
/// <summary>
/// Creates a fully mocked test setup for Focus Energy tests.
/// </summary>
private static (FocusEnergy focusEnergy, IExecutingMove move, IPokemon target, IScriptSet volatileSet, IHitData
hitData) CreateTestSetup()
private static (FocusEnergy focusEnergy, IExecutingMove move, IBattlePokemon target, IScriptSet volatileSet,
IHitData hitData) CreateTestSetup()
{
var focusEnergy = new FocusEnergy();
var move = Substitute.For<IExecutingMove>();
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
var hitData = Substitute.For<IHitData>();
move.GetHitData(target, 0).Returns(hitData);

View File

@@ -16,27 +16,18 @@ public class FocusPunchTests
/// <summary>
/// Creates a fully mocked test setup for the turn start (charging) phase of Focus Punch.
/// </summary>
private static (FocusPunch focusPunch, ITurnChoice choice, IPokemon user, IScriptSet volatileSet, EventHook
eventHook) CreateChargeSetup(bool hasBattleData = true)
private static (FocusPunch focusPunch, ITurnChoice choice, IBattlePokemon user, IScriptSet volatileSet, EventHook
eventHook) CreateChargeSetup()
{
var focusPunch = new FocusPunch();
var user = Substitute.For<IPokemon>();
var user = Substitute.For<IBattlePokemon>();
var volatileSet = Substitute.For<IScriptSet>();
user.Volatile.Returns(volatileSet);
var eventHook = new EventHook();
if (hasBattleData)
{
var battle = Substitute.For<IBattle>();
battle.EventHook.Returns(eventHook);
var battleData = Substitute.For<IPokemonBattleData>();
battleData.Battle.Returns(battle);
user.BattleData.Returns(battleData);
}
else
{
user.BattleData.Returns((IPokemonBattleData?)null);
}
var battle = Substitute.For<IBattle>();
battle.EventHook.Returns(eventHook);
user.Battle.Returns(battle);
var choice = Substitute.For<ITurnChoice>();
choice.User.Returns(user);
@@ -50,7 +41,7 @@ public class FocusPunchTests
{
var focusPunch = new FocusPunch();
var move = Substitute.For<IExecutingMove>();
var user = Substitute.For<IPokemon>();
var user = Substitute.For<IBattlePokemon>();
var volatileSet = Substitute.For<IScriptSet>();
user.Volatile.Returns(volatileSet);
move.User.Returns(user);
@@ -64,8 +55,8 @@ public class FocusPunchTests
private static FocusPunchEffect CreateHitEffect()
{
var effect = new FocusPunchEffect();
var hitReceiver = Substitute.For<IPokemon>();
hitReceiver.BattleData.Returns((IPokemonBattleData?)null);
var hitReceiver = Substitute.For<IBattlePokemon>();
hitReceiver.Battle.EventHook.Returns(new EventHook());
effect.OnIncomingHit(Substitute.For<IExecutingMove>(), hitReceiver, 0);
return effect;
}
@@ -118,26 +109,6 @@ public class FocusPunchTests
await Assert.That(ReferenceEquals(capturedEvent.Parameters["pokemon"], user)).IsTrue();
}
/// <summary>
/// Bulbapedia: "The user of Focus Punch will start focusing at the beginning of the turn the move is used".
/// Technical test: if the user has no <see cref="IPokemon.BattleData"/>, no dialog can be shown, but the
/// charging effect is still applied without throwing.
/// </summary>
[Test]
public async Task OnBeforeTurnStart_UserWithoutBattleData_StillAddsFocusPunchEffect()
{
// Arrange
var (focusPunch, choice, _, volatileSet, _) = CreateChargeSetup(false);
// Act
focusPunch.OnBeforeTurnStart(choice);
// Assert
var addCall = volatileSet.ReceivedCalls().FirstOrDefault(c => c.GetMethodInfo().Name == "Add");
await Assert.That(addCall).IsNotNull();
await Assert.That(addCall!.GetArguments()[0] is FocusPunchEffect).IsTrue();
}
/// <summary>
/// Bulbapedia: "then execute the move Focus Punch at a decreased priority, unless it was hit by another
/// Pokémon's damaging move before executing Focus Punch".
@@ -217,8 +188,7 @@ public class FocusPunchTests
var effect = new FocusPunchEffect();
var move = Substitute.For<IExecutingMove>();
move.UseMove.Returns(fissure!);
var target = Substitute.For<IPokemon>();
target.BattleData.Returns((IPokemonBattleData?)null);
var target = Substitute.For<IBattlePokemon>();
// Act
effect.OnIncomingHit(move, target, 0);

View File

@@ -40,23 +40,22 @@ public class FollowMeTests
/// <summary>
/// Creates a mocked Pokémon standing on the given side of the given <see cref="Arena"/>.
/// </summary>
private static IPokemon CreatePokemon(Arena arena, byte sideIndex)
private static IBattlePokemon CreatePokemon(Arena arena, byte sideIndex)
{
var pokemon = Substitute.For<IPokemon>();
var pokemon = Substitute.For<IBattlePokemon>();
pokemon.Volatile.Returns(Substitute.For<IScriptSet>());
var battleData = Substitute.For<IPokemonBattleData>();
battleData.SideIndex.Returns(sideIndex);
battleData.BattleSide.Returns(arena.Sides[sideIndex]);
battleData.Battle.Returns(arena.Battle);
pokemon.BattleData.Returns(battleData);
pokemon.SideIndex.Returns(sideIndex);
pokemon.BattleSide.Returns(arena.Sides[sideIndex]);
pokemon.Battle.Returns(arena.Battle);
return pokemon;
}
/// <summary>
/// Creates a mocked move choice made by the given Pokémon, using the named move.
/// </summary>
private static IMoveChoice CreateMoveChoice(IPokemon user, string moveName = "tackle", bool noRedirection = false)
private static IMoveChoice CreateMoveChoice(IBattlePokemon user, string moveName = "tackle",
bool noRedirection = false)
{
var moveData = Substitute.For<IMoveData>();
moveData.Name.Returns(new StringKey(moveName));
@@ -94,7 +93,7 @@ public class FollowMeTests
/// Executes <see cref="FollowMe"/> for the given user and returns the center-of-attention effect it
/// registered, regardless of which script set it chose to register it on.
/// </summary>
private static Script? MakeCenterOfAttention(IPokemon user)
private static Script? MakeCenterOfAttention(IBattlePokemon user)
{
var followMe = new FollowMe();
var move = Substitute.For<IExecutingMove>();
@@ -102,14 +101,14 @@ public class FollowMeTests
followMe.OnSecondaryEffect(move, user, 0);
return RegisteredScript(user.Volatile) ?? RegisteredScript(user.BattleData!.BattleSide.VolatileScripts);
return RegisteredScript(user.Volatile) ?? RegisteredScript(user.BattleSide.VolatileScripts);
}
/// <summary>
/// Drives the registered effect through whichever target-redirection hook it implements, mirroring the two
/// hooks the engine runs while resolving a move's targets.
/// </summary>
private static void Redirect(Script effect, IMoveChoice moveChoice, ref IReadOnlyList<IPokemon?> targets)
private static void Redirect(Script effect, IMoveChoice moveChoice, ref IReadOnlyList<IBattlePokemon?> targets)
{
switch (effect)
{
@@ -146,7 +145,7 @@ public class FollowMeTests
/// The engine resolves a move's targets by running <see cref="IScriptChangeTargets"/> over the attacker's
/// own script scope, and <see cref="IScriptChangeIncomingTargets"/> over each intended target's scope —
/// which covers that target's own scripts and its <see cref="IBattleSide.VolatileScripts"/>. A redirection
/// effect stored on the center of attention's <see cref="IPokemon.Volatile"/> therefore sits in neither
/// effect stored on the center of attention's <see cref="IBattlePokemon.Volatile"/> therefore sits in neither
/// scope when an opponent attacks the center's ally. It must live on the center's side, the way
/// <see cref="PkmnLib.Plugin.Gen7.Scripts.Side.RagePowderEffect"/> does.
/// </summary>
@@ -200,7 +199,7 @@ public class FollowMeTests
var opponent = CreatePokemon(arena, OpposingSide);
var effect = MakeCenterOfAttention(center)!;
var moveChoice = CreateMoveChoice(opponent);
IReadOnlyList<IPokemon?> targets = [ally];
IReadOnlyList<IBattlePokemon?> targets = [ally];
// Act
Redirect(effect, moveChoice, ref targets);
@@ -223,7 +222,7 @@ public class FollowMeTests
var opponent = CreatePokemon(arena, OpposingSide);
var effect = MakeCenterOfAttention(center)!;
var moveChoice = CreateMoveChoice(opponent);
IReadOnlyList<IPokemon?> targets = [center, ally];
IReadOnlyList<IBattlePokemon?> targets = [center, ally];
// Act
Redirect(effect, moveChoice, ref targets);
@@ -248,7 +247,7 @@ public class FollowMeTests
var targetedAlly = CreatePokemon(arena, CenterSide);
var effect = MakeCenterOfAttention(center)!;
var moveChoice = CreateMoveChoice(attackingAlly);
IReadOnlyList<IPokemon?> targets = [targetedAlly];
IReadOnlyList<IBattlePokemon?> targets = [targetedAlly];
// Act
Redirect(effect, moveChoice, ref targets);
@@ -271,7 +270,7 @@ public class FollowMeTests
var opponent = CreatePokemon(arena, OpposingSide);
var effect = MakeCenterOfAttention(center)!;
var moveChoice = CreateMoveChoice(opponent);
IReadOnlyList<IPokemon?> targets = [center];
IReadOnlyList<IBattlePokemon?> targets = [center];
// Act
Redirect(effect, moveChoice, ref targets);
@@ -287,7 +286,7 @@ public class FollowMeTests
/// </summary>
private static IScriptSet CreateHostedSet(Script effect)
{
var owner = Substitute.For<IPokemon>();
var owner = Substitute.For<IBattlePokemon>();
owner.GetScripts().Returns(_ => new ScriptIterator(Array.Empty<IEnumerable<ScriptContainer>>()));
IScriptSet set = new ScriptSet(owner);
set.Add(effect);
@@ -369,7 +368,7 @@ public class FollowMeTests
var opponent = CreatePokemon(arena, OpposingSide);
var effect = MakeCenterOfAttention(center)!;
var moveChoice = CreateMoveChoice(opponent, moveName, true);
IReadOnlyList<IPokemon?> targets = [ally];
IReadOnlyList<IBattlePokemon?> targets = [ally];
// Act
Redirect(effect, moveChoice, ref targets);
@@ -391,7 +390,7 @@ public class FollowMeTests
var opponent = CreatePokemon(arena, OpposingSide);
var effect = MakeCenterOfAttention(center)!;
var moveChoice = CreateMoveChoice(opponent);
IReadOnlyList<IPokemon?> targets = [];
IReadOnlyList<IBattlePokemon?> targets = [];
// Act
Redirect(effect, moveChoice, ref targets);

View File

@@ -22,12 +22,12 @@ public class ForesightTests
/// Creates a fully mocked test setup for Foresight tests. The target is in battle, with a mocked
/// battle library chain and the given evasion stat stage.
/// </summary>
private static (Foresight script, IExecutingMove move, IPokemon target, IScriptSet volatileSet, IHitData hitData)
CreateTestSetup(sbyte evasionStage = 0)
private static (Foresight script, IExecutingMove move, IBattlePokemon target, IScriptSet volatileSet, IHitData
hitData) CreateTestSetup(sbyte evasionStage = 0)
{
var script = new Foresight();
var move = Substitute.For<IExecutingMove>();
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
var hitData = Substitute.For<IHitData>();
move.GetHitData(target, 0).Returns(hitData);
@@ -38,9 +38,7 @@ public class ForesightTests
library.StaticLibrary.Returns(staticLibrary);
var battle = Substitute.For<IBattle>();
battle.Library.Returns(library);
var battleData = Substitute.For<IPokemonBattleData>();
battleData.Battle.Returns(battle);
target.BattleData.Returns(battleData);
target.Battle.Returns(battle);
var volatileSet = Substitute.For<IScriptSet>();
target.Volatile.Returns(volatileSet);
@@ -54,9 +52,9 @@ public class ForesightTests
/// <summary>
/// Helper to extract all stat boost changes requested on the target through
/// <see cref="IPokemon.ChangeStatBoost"/>.
/// <see cref="IBattlePokemon.ChangeStatBoost"/>.
/// </summary>
private static IReadOnlyList<(Statistic stat, sbyte amount)> GetStatBoostChanges(IPokemon target) =>
private static IReadOnlyList<(Statistic stat, sbyte amount)> GetStatBoostChanges(IBattlePokemon target) =>
target.ReceivedCalls().Where(c => c.GetMethodInfo().Name == "ChangeStatBoost")
.Select(c => ((Statistic)c.GetArguments()[0]!, (sbyte)c.GetArguments()[1]!)).ToList();
@@ -143,25 +141,6 @@ public class ForesightTests
await Assert.That(changes.Any(c => c.amount != 0)).IsFalse();
}
/// <summary>
/// If the target has no <see cref="IPokemon.BattleData"/>, the script cannot resolve the type library,
/// so nothing may happen: no volatile script is added and no stat stage is changed.
/// </summary>
[Test]
public async Task OnSecondaryEffect_NullBattleData_NoEffect()
{
// Arrange
var (script, move, target, volatileSet, _) = CreateTestSetup(2);
target.BattleData.Returns((IPokemonBattleData?)null);
// Act
script.OnSecondaryEffect(move, target, 0);
// Assert
await Assert.That(volatileSet.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "Add")).IsFalse();
await Assert.That(target.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "ChangeStatBoost")).IsFalse();
}
/// <summary>
/// Bulbapedia (Generations V to VII): "Foresight will once again fail if used against a Pokémon already
/// under its effect."

View File

@@ -22,12 +22,12 @@ public class ForestsCurseTests
/// Creates a fully mocked test setup for Forest's Curse tests. The target is in battle, with a mocked
/// battle library chain that can resolve the Grass type identifier.
/// </summary>
private static (ForestsCurse script, IExecutingMove move, IPokemon target, IHitData hitData) CreateTestSetup(
private static (ForestsCurse script, IExecutingMove move, IBattlePokemon target, IHitData hitData) CreateTestSetup(
bool grassTypeInLibrary = true, TypeIdentifier[]? targetTypes = null)
{
var script = new ForestsCurse();
var move = Substitute.For<IExecutingMove>();
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
var hitData = Substitute.For<IHitData>();
move.GetHitData(target, 0).Returns(hitData);
@@ -47,18 +47,16 @@ public class ForestsCurseTests
library.StaticLibrary.Returns(staticLibrary);
var battle = Substitute.For<IBattle>();
battle.Library.Returns(library);
var battleData = Substitute.For<IPokemonBattleData>();
battleData.Battle.Returns(battle);
target.BattleData.Returns(battleData);
target.Battle.Returns(battle);
target.Types.Returns(targetTypes ?? [NormalType]);
return (script, move, target, hitData);
}
/// <summary>
/// Helper to extract the type passed to the target's <see cref="IPokemon.AddType"/> call.
/// Helper to extract the type passed to the target's <see cref="IBattlePokemon.AddType"/> call.
/// </summary>
private static TypeIdentifier? GetAddedType(IPokemon target)
private static TypeIdentifier? GetAddedType(IBattlePokemon target)
{
var call = target.ReceivedCalls().FirstOrDefault(c => c.GetMethodInfo().Name == "AddType");
return call != null ? (TypeIdentifier)call.GetArguments()[0]! : null;
@@ -85,7 +83,7 @@ public class ForestsCurseTests
/// <summary>
/// Bulbapedia: the Grass type is added "in addition to the Pokémon's original type(s)".
/// The target's original types may not be replaced, so <see cref="IPokemon.SetTypes"/> must not be used.
/// The target's original types may not be replaced, so <see cref="IBattlePokemon.SetTypes"/> must not be used.
/// </summary>
[Test]
public async Task OnSecondaryEffect_TargetInBattle_OriginalTypesNotReplaced()
@@ -117,24 +115,6 @@ public class ForestsCurseTests
await Assert.That(hitData.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "Fail")).IsTrue();
}
/// <summary>
/// If the target has no <see cref="IPokemon.BattleData"/>, the script cannot resolve the type library,
/// so no type may be added.
/// </summary>
[Test]
public async Task OnSecondaryEffect_NullBattleData_DoesNotAddType()
{
// Arrange
var (script, move, target, _) = CreateTestSetup();
target.BattleData.Returns((IPokemonBattleData?)null);
// Act
script.OnSecondaryEffect(move, target, 0);
// Assert
await Assert.That(target.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "AddType")).IsFalse();
}
/// <summary>
/// Technical test: when the type library cannot resolve the Grass type identifier, the script must
/// bail out without adding a type.
@@ -158,8 +138,8 @@ public class ForestsCurseTests
/// A target whose Ghost type was added by <see cref="TrickOrTreat"/> must end up with the Grass type and
/// without the Ghost type, while keeping its original type. The mocked target uses a real
/// <see cref="ScriptSet"/> for its volatile scripts, so the <c>HasHadTypeAddedEffect</c> marker set by
/// Trick-or-Treat is visible to Forest's Curse, and reacts to <see cref="IPokemon.AddType"/> and
/// <see cref="IPokemon.RemoveType"/> with a backing type list, mirroring the real Pokemon implementation,
/// Trick-or-Treat is visible to Forest's Curse, and reacts to <see cref="IBattlePokemon.AddType"/> and
/// <see cref="IBattlePokemon.RemoveType"/> with a backing type list, mirroring the real Pokemon implementation,
/// so the resulting type list is observable.
/// </summary>
[Test]
@@ -169,7 +149,7 @@ public class ForestsCurseTests
var forestsCurse = new ForestsCurse();
var trickOrTreat = new TrickOrTreat();
var move = Substitute.For<IExecutingMove>();
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
var typeLibrary = Substitute.For<IReadOnlyTypeLibrary>();
typeLibrary.TryGetTypeIdentifier(new StringKey("grass"), out Arg.Any<TypeIdentifier>()).Returns(x =>
@@ -189,9 +169,7 @@ public class ForestsCurseTests
var battle = Substitute.For<IBattle>();
battle.Library.Returns(library);
move.Battle.Returns(battle);
var battleData = Substitute.For<IPokemonBattleData>();
battleData.Battle.Returns(battle);
target.BattleData.Returns(battleData);
target.Battle.Returns(battle);
// Real script set so the HasHadTypeAddedEffect marker added by Trick-or-Treat is visible
// to Forest's Curse.

View File

@@ -15,7 +15,7 @@ public class FoulPlayTests
/// Creates a fully mocked test setup for Foul Play tests, with a target whose (boosted) offensive
/// stats are set to the given values.
/// </summary>
private static (FoulPlay script, IExecutingMove move, IPokemon target) CreateTestSetup(MoveCategory category,
private static (FoulPlay script, IExecutingMove move, IBattlePokemon target) CreateTestSetup(MoveCategory category,
uint targetAttack, uint targetSpecialAttack)
{
var script = new FoulPlay();
@@ -24,10 +24,10 @@ public class FoulPlayTests
useMove.Category.Returns(category);
move.UseMove.Returns(useMove);
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
target.BoostedStats.Returns(new StatisticSet<uint>(100, targetAttack, 80, targetSpecialAttack, 80, 80));
var user = Substitute.For<IPokemon>();
var user = Substitute.For<IBattlePokemon>();
move.User.Returns(user);
return (script, move, target);
@@ -54,8 +54,8 @@ public class FoulPlayTests
/// <summary>
/// Bulbapedia: "The target's Attack stat stage-modifiers are applied (rather than the user's)".
/// The stat used must be the target's boosted Attack (<see cref="IPokemon.BoostedStats"/>, which includes
/// stat stages), not its flat Attack (<see cref="IPokemon.FlatStats"/>).
/// The stat used must be the target's boosted Attack (<see cref="IBattlePokemon.BoostedStats"/>, which includes
/// stat stages), not its flat Attack (<see cref="IBattlePokemon.FlatStats"/>).
/// </summary>
[Test]
public async Task ChangeOffensiveStatValue_PhysicalMove_TargetStatStagesApplied()

View File

@@ -17,7 +17,7 @@ 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)
private static (FreezeDry script, IExecutingMove move, IBattlePokemon target, IDynamicLibrary library)
CreateEffectivenessSetup(params string[] targetTypes)
{
var script = new FreezeDry();
@@ -25,18 +25,16 @@ public class FreezeDryTests
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);
var target = Substitute.For<IBattlePokemon>();
target.Types.Returns(targetTypes.Select(name => GetTypeId(library, name)).ToList());
target.Battle.Returns(battle);
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.GetHitData(Arg.Any<IBattlePokemon>(), Arg.Any<byte>()).Returns(hitData);
var user = Substitute.For<IBattlePokemon>();
move.User.Returns(user);
return (script, move, target, library);
@@ -46,24 +44,21 @@ public class FreezeDryTests
/// 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)
private static (FreezeDry script, IExecutingMove move, IBattlePokemon target, IBattlePokemon 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>())
random.EffectChance(Arg.Any<float>(), Arg.Any<IExecutingMove>(), Arg.Any<IBattlePokemon>(), 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 target = Substitute.For<IBattlePokemon>();
target.Battle.Returns(battle);
var move = Substitute.For<IExecutingMove>();
var user = Substitute.For<IPokemon>();
var user = Substitute.For<IBattlePokemon>();
move.User.Returns(user);
return (script, move, target, user, random);
@@ -139,25 +134,6 @@ public class FreezeDryTests
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.
@@ -213,22 +189,4 @@ public class FreezeDryTests
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();
}
}

View File

@@ -17,23 +17,21 @@ public class FreezeShockTests
/// <summary>
/// Creates a fully mocked test setup for Freeze Shock secondary effect tests.
/// </summary>
private static (FreezeShock script, IExecutingMove move, IPokemon target, IPokemon user, IBattleRandom random)
CreateTestSetup()
private static (FreezeShock script, IExecutingMove move, IBattlePokemon target, IBattlePokemon user, IBattleRandom
random) CreateTestSetup()
{
var script = new FreezeShock();
var move = Substitute.For<IExecutingMove>();
var user = Substitute.For<IPokemon>();
var user = Substitute.For<IBattlePokemon>();
move.User.Returns(user);
var battle = Substitute.For<IBattle>();
var random = Substitute.For<IBattleRandom>();
battle.Random.Returns(random);
var battleData = Substitute.For<IPokemonBattleData>();
battleData.Battle.Returns(battle);
var target = Substitute.For<IPokemon>();
target.BattleData.Returns(battleData);
var target = Substitute.For<IBattlePokemon>();
target.Battle.Returns(battle);
return (script, move, target, user, random);
}
@@ -92,24 +90,6 @@ public class FreezeShockTests
await Assert.That(random.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "EffectChance")).IsTrue();
}
/// <summary>
/// Technical test: outside of battle (no battle data on the target) the secondary effect does nothing
/// and does not throw.
/// </summary>
[Test]
public async Task OnSecondaryEffect_TargetHasNoBattleData_DoesNotParalyzeTarget()
{
// Arrange
var (script, move, target, _, _) = CreateTestSetup();
target.BattleData.Returns((IPokemonBattleData?)null);
// Act
script.OnSecondaryEffect(move, target, 0);
// Assert
await Assert.That(target.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "SetStatus")).IsFalse();
}
/// <summary>
/// Bulbapedia: the user is "cloaked in a freezing light" on the charge turn and attacks on the following
/// turn. The concrete script provides the <see cref="RequireChargeEffect"/> volatile that forces the user
@@ -120,7 +100,7 @@ public class FreezeShockTests
{
// Arrange
var script = new FreezeShock();
var user = Substitute.For<IPokemon>();
var user = Substitute.For<IBattlePokemon>();
// Act
var chargeEffect = script.CreateVolatile(user);

View File

@@ -15,12 +15,12 @@ public class FrustrationTests
/// <summary>
/// Creates a fully mocked test setup with a user that has the given friendship value.
/// </summary>
private static (Frustration script, IExecutingMove move, IPokemon target) CreateTestSetup(byte friendship)
private static (Frustration script, IExecutingMove move, IBattlePokemon target) CreateTestSetup(byte friendship)
{
var script = new Frustration();
var move = Substitute.For<IExecutingMove>();
var target = Substitute.For<IPokemon>();
var user = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
var user = Substitute.For<IBattlePokemon>();
user.Happiness.Returns(friendship);
move.User.Returns(user);
return (script, move, target);

View File

@@ -20,13 +20,13 @@ public class FuryCutterTests
/// Creates a fully mocked test setup where the user's volatile scripts are a real
/// <see cref="ScriptSet"/>, so the <see cref="FuryCutterEffect"/> added by the move can be inspected.
/// </summary>
private static (FuryCutter script, IExecutingMove move, IPokemon target, IPokemon user, ScriptSet userVolatile)
CreateTestSetup()
private static (FuryCutter script, IExecutingMove move, IBattlePokemon target, IBattlePokemon user, ScriptSet
userVolatile) CreateTestSetup()
{
var script = new FuryCutter();
var move = Substitute.For<IExecutingMove>();
var target = Substitute.For<IPokemon>();
var user = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
var user = Substitute.For<IBattlePokemon>();
var userVolatile = new ScriptSet(user);
user.Volatile.Returns(userVolatile);
user.GetScripts().Returns(_ => new ScriptIterator(Array.Empty<IEnumerable<ScriptContainer>>()));
@@ -38,7 +38,8 @@ public class FuryCutterTests
/// Simulates the given number of consecutive uses of Fury Cutter, feeding the Gen VII base power of 40
/// into each use, and returns the base power of the final use.
/// </summary>
private static ushort SimulateConsecutiveUses(FuryCutter script, IExecutingMove move, IPokemon target, int uses)
private static ushort SimulateConsecutiveUses(FuryCutter script, IExecutingMove move, IBattlePokemon target,
int uses)
{
ushort basePower = 40;
for (var i = 0; i < uses; i++)
@@ -127,7 +128,7 @@ public class FuryCutterTests
public async Task OnBeforeMove_DifferentMoveUsed_EffectRemovesItself()
{
// Arrange
var user = Substitute.For<IPokemon>();
var user = Substitute.For<IBattlePokemon>();
user.GetScripts().Returns(_ => new ScriptIterator(Array.Empty<IEnumerable<ScriptContainer>>()));
var userVolatile = new ScriptSet(user);
var effect = new FuryCutterEffect();
@@ -153,7 +154,7 @@ public class FuryCutterTests
public async Task OnBeforeMove_FuryCutterUsedAgain_EffectPersists()
{
// Arrange
var user = Substitute.For<IPokemon>();
var user = Substitute.For<IBattlePokemon>();
user.GetScripts().Returns(_ => new ScriptIterator(Array.Empty<IEnumerable<ScriptContainer>>()));
var userVolatile = new ScriptSet(user);
var effect = new FuryCutterEffect();

View File

@@ -25,7 +25,7 @@ public class FusionBoltTests
moveData.SecondaryEffect.Returns((ISecondaryEffect?)null);
var learnedMove = Substitute.For<ILearnedMove>();
learnedMove.MoveData.Returns(moveData);
var user = Substitute.For<IPokemon>();
var user = Substitute.For<IBattlePokemon>();
var choice = new MoveChoice(user, learnedMove, 0, 0);
if (failed)
choice.Fail();
@@ -36,7 +36,7 @@ public class FusionBoltTests
/// Creates a fully mocked test setup where the current turn consists of the given choices, executed in
/// order, and the script is evaluating the given executing choice.
/// </summary>
private static (FusionBolt script, IExecutingMove move, IPokemon target) CreateTestSetup(
private static (FusionBolt script, IExecutingMove move, IBattlePokemon target) CreateTestSetup(
IMoveChoice executingChoice, params ITurnChoice[] currentTurnChoices)
{
var script = new FusionBolt();
@@ -46,11 +46,9 @@ public class FusionBoltTests
var battle = Substitute.For<IBattle>();
battle.PreviousTurnChoices.Returns(new List<IReadOnlyList<ITurnChoice>> { currentTurnChoices });
var battleData = Substitute.For<IPokemonBattleData>();
battleData.Battle.Returns(battle);
var target = Substitute.For<IPokemon>();
target.BattleData.Returns(battleData);
var target = Substitute.For<IBattlePokemon>();
target.Battle.Returns(battle);
return (script, move, target);
}
@@ -135,10 +133,8 @@ public class FusionBoltTests
new ITurnChoice[] { boltChoice },
});
var battleData = Substitute.For<IPokemonBattleData>();
battleData.Battle.Returns(battle);
var target = Substitute.For<IPokemon>();
target.BattleData.Returns(battleData);
var target = Substitute.For<IBattlePokemon>();
target.Battle.Returns(battle);
var modifier = 1f;
// Act
@@ -229,25 +225,4 @@ public class FusionBoltTests
// Assert
await Assert.That(modifier).IsEqualTo(1f);
}
/// <summary>
/// Technical test: outside of battle (no battle data on the target) the modifier is left unchanged and
/// the hook does not throw.
/// </summary>
[Test]
public async Task ChangeDamageModifier_TargetHasNoBattleData_ModifierUnchanged()
{
// Arrange
var script = new FusionBolt();
var move = Substitute.For<IExecutingMove>();
var target = Substitute.For<IPokemon>();
target.BattleData.Returns((IPokemonBattleData?)null);
var modifier = 1f;
// Act
script.ChangeDamageModifier(move, target, 0, ref modifier);
// Assert
await Assert.That(modifier).IsEqualTo(1f);
}
}

View File

@@ -25,7 +25,7 @@ public class FusionFlareTests
moveData.SecondaryEffect.Returns((ISecondaryEffect?)null);
var learnedMove = Substitute.For<ILearnedMove>();
learnedMove.MoveData.Returns(moveData);
var user = Substitute.For<IPokemon>();
var user = Substitute.For<IBattlePokemon>();
var choice = new MoveChoice(user, learnedMove, 0, 0);
if (failed)
choice.Fail();
@@ -36,7 +36,7 @@ public class FusionFlareTests
/// Creates a fully mocked test setup where the current turn consists of the given choices, executed in
/// order, and the script is evaluating the given executing choice.
/// </summary>
private static (FusionFlare script, IExecutingMove move, IPokemon target) CreateTestSetup(
private static (FusionFlare script, IExecutingMove move, IBattlePokemon target) CreateTestSetup(
IMoveChoice executingChoice, params ITurnChoice[] currentTurnChoices)
{
var script = new FusionFlare();
@@ -46,11 +46,9 @@ public class FusionFlareTests
var battle = Substitute.For<IBattle>();
battle.PreviousTurnChoices.Returns(new List<IReadOnlyList<ITurnChoice>> { currentTurnChoices });
var battleData = Substitute.For<IPokemonBattleData>();
battleData.Battle.Returns(battle);
var target = Substitute.For<IPokemon>();
target.BattleData.Returns(battleData);
var target = Substitute.For<IBattlePokemon>();
target.Battle.Returns(battle);
return (script, move, target);
}
@@ -135,10 +133,8 @@ public class FusionFlareTests
new ITurnChoice[] { flareChoice },
});
var battleData = Substitute.For<IPokemonBattleData>();
battleData.Battle.Returns(battle);
var target = Substitute.For<IPokemon>();
target.BattleData.Returns(battleData);
var target = Substitute.For<IBattlePokemon>();
target.Battle.Returns(battle);
var modifier = 1f;
// Act
@@ -229,25 +225,4 @@ public class FusionFlareTests
// Assert
await Assert.That(modifier).IsEqualTo(1f);
}
/// <summary>
/// Technical test: outside of battle (no battle data on the target) the modifier is left unchanged and
/// the hook does not throw.
/// </summary>
[Test]
public async Task ChangeDamageModifier_TargetHasNoBattleData_ModifierUnchanged()
{
// Arrange
var script = new FusionFlare();
var move = Substitute.For<IExecutingMove>();
var target = Substitute.For<IPokemon>();
target.BattleData.Returns((IPokemonBattleData?)null);
var modifier = 1f;
// Act
script.ChangeDamageModifier(move, target, 0, ref modifier);
// Assert
await Assert.That(modifier).IsEqualTo(1f);
}
}

View File

@@ -23,7 +23,7 @@ public class FutureSightTests
/// Creates a fully mocked test setup for the <see cref="FutureSight"/> script where the battle's
/// volatile scripts are a real <see cref="ScriptSet"/>.
/// </summary>
private static (FutureSight script, IExecutingMove move, IPokemon user, IScriptSet battleVolatile)
private static (FutureSight script, IExecutingMove move, IBattlePokemon user, IScriptSet battleVolatile)
CreateScriptSetup()
{
var script = new FutureSight();
@@ -34,11 +34,9 @@ public class FutureSightTests
IScriptSet battleVolatile = new ScriptSet(battle);
battle.Volatile.Returns(battleVolatile);
var battleData = Substitute.For<IPokemonBattleData>();
battleData.Battle.Returns(battle);
var user = Substitute.For<IPokemon>();
user.BattleData.Returns(battleData);
var user = Substitute.For<IBattlePokemon>();
move.User.Returns(user);
user.Battle.Returns(battle);
var moveChoice = Substitute.For<IMoveChoice>();
move.MoveChoice.Returns(moveChoice);
@@ -50,8 +48,8 @@ public class FutureSightTests
/// Creates a fully mocked test setup for the <see cref="FutureSightEffect"/>, with a target at side 1,
/// position 0 and a damage calculator that computes the given damage when the delayed strike lands.
/// </summary>
private static (FutureSightEffect effect, IBattle battle, IPokemon target, EventHook eventHook) CreateEffectSetup(
uint damage = 100, bool targetUsable = true)
private static (FutureSightEffect effect, IBattle battle, IBattlePokemon target, EventHook eventHook)
CreateEffectSetup(uint damage = 100, bool targetUsable = true)
{
var moveData = Substitute.For<IMoveData>();
var learnedMove = Substitute.For<ILearnedMove>();
@@ -65,12 +63,13 @@ public class FutureSightTests
var eventHook = new EventHook();
battle.EventHook.Returns(eventHook);
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
target.IsUsable.Returns(targetUsable);
battle.GetPokemon(1, 0).Returns(target);
battle.Library.DamageCalculator.GetDamage(Arg.Any<IExecutingMove?>(), Arg.Any<MoveCategory>(),
Arg.Any<IPokemon>(), Arg.Any<IPokemon>(), Arg.Any<int>(), Arg.Any<byte>(), Arg.Any<IHitData>())
Arg.Any<IBattlePokemon>(), Arg.Any<IBattlePokemon>(), Arg.Any<int>(), Arg.Any<byte>(),
Arg.Any<IHitData>())
.Returns(damage);
var effect = new FutureSightEffect(moveChoice);
@@ -113,25 +112,6 @@ public class FutureSightTests
await Assert.That(battleVolatile.Contains(ScriptUtils.ResolveName<FutureSightEffect>())).IsTrue();
}
/// <summary>
/// Technical test: outside of battle (no battle data on the user) the hook does nothing and does not
/// stop the move.
/// </summary>
[Test]
public async Task StopBeforeMove_UserHasNoBattleData_DoesNotStopMove()
{
// Arrange
var (script, move, user, _) = CreateScriptSetup();
user.BattleData.Returns((IPokemonBattleData?)null);
var stop = false;
// Act
script.StopBeforeMove(move, ref stop);
// Assert
await Assert.That(stop).IsFalse();
}
/// <summary>
/// Bulbapedia: "Two turns later, Future Sight will do damage against the target." One end-of-turn tick
/// after selection (the selection turn itself) plus one more is not enough for the strike to land.
@@ -244,10 +224,8 @@ public class FutureSightTests
moveChoice.TargetSide.Returns(targetSide);
moveChoice.TargetPosition.Returns(targetPosition);
var battleData = Substitute.For<IPokemonBattleData>();
battleData.Battle.Returns(battle);
var user = Substitute.For<IPokemon>();
user.BattleData.Returns(battleData);
var user = Substitute.For<IBattlePokemon>();
user.Battle.Returns(battle);
var move = Substitute.For<IExecutingMove>();
move.User.Returns(user);
@@ -325,15 +303,16 @@ public class FutureSightTests
var (battle, battleVolatile) = CreateBattleWithRealVolatile();
battle.EventHook.Returns(new EventHook());
var targetA = Substitute.For<IPokemon>();
var targetA = Substitute.For<IBattlePokemon>();
targetA.IsUsable.Returns(true);
battle.GetPokemon(1, 0).Returns(targetA);
var targetB = Substitute.For<IPokemon>();
var targetB = Substitute.For<IBattlePokemon>();
targetB.IsUsable.Returns(true);
battle.GetPokemon(1, 1).Returns(targetB);
battle.Library.DamageCalculator.GetDamage(Arg.Any<IExecutingMove?>(), Arg.Any<MoveCategory>(),
Arg.Any<IPokemon>(), Arg.Any<IPokemon>(), Arg.Any<int>(), Arg.Any<byte>(), Arg.Any<IHitData>())
Arg.Any<IBattlePokemon>(), Arg.Any<IBattlePokemon>(), Arg.Any<int>(), Arg.Any<byte>(),
Arg.Any<IHitData>())
.Returns(100u);
var useA = CreateFutureSightUse(battle, 1, 0);

View File

@@ -10,7 +10,7 @@ namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves;
/// <remarks>
/// Bulbapedia also lists Abilities the move fails against (Multitype, Stance Change, ...); that check is
/// implemented engine-side through <see cref="PkmnLib.Static.Species.IAbility.CanBeChanged"/> inside
/// <see cref="IPokemon.SuppressAbility"/>, so it is not the responsibility of this script.
/// <see cref="IBattlePokemon.SuppressAbility"/>, so it is not the responsibility of this script.
/// </remarks>
public class GastroAcidTests
{
@@ -24,7 +24,7 @@ public class GastroAcidTests
// Arrange
var script = new GastroAcid();
var move = Substitute.For<IExecutingMove>();
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
// Act
script.OnSecondaryEffect(move, target, 0);
@@ -44,9 +44,9 @@ public class GastroAcidTests
// Arrange
var script = new GastroAcid();
var move = Substitute.For<IExecutingMove>();
var user = Substitute.For<IPokemon>();
var user = Substitute.For<IBattlePokemon>();
move.User.Returns(user);
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
// Act
script.OnSecondaryEffect(move, target, 0);

View File

@@ -17,8 +17,8 @@ public class GearUpTests
/// Creates a fully mocked test setup for Gear Up tests. The user is on side 0; the given side lists
/// are installed as the battle's sides.
/// </summary>
private static (GearUp script, IExecutingMove move, IPokemon user) CreateTestSetup(
params IReadOnlyList<IPokemon?>[] sidePokemon)
private static (GearUp script, IExecutingMove move, IBattlePokemon user) CreateTestSetup(
params IReadOnlyList<IBattlePokemon?>[] sidePokemon)
{
var script = new GearUp();
var move = Substitute.For<IExecutingMove>();
@@ -32,23 +32,21 @@ public class GearUpTests
}).ToArray();
battle.Sides.Returns(sides);
var user = Substitute.For<IPokemon>();
var battleData = Substitute.For<IPokemonBattleData>();
battleData.Battle.Returns(battle);
battleData.SideIndex.Returns((byte)0);
user.BattleData.Returns(battleData);
var user = Substitute.For<IBattlePokemon>();
user.Battle.Returns(battle);
user.SideIndex.Returns((byte)0);
move.User.Returns(user);
return (script, move, user);
}
/// <summary>
/// Creates a mocked Pokémon whose <see cref="IPokemon.ActiveAbility"/> has the given name, or no
/// Creates a mocked Pokémon whose <see cref="IBattlePokemon.ActiveAbility"/> has the given name, or no
/// ability at all when <paramref name="abilityName"/> is null.
/// </summary>
private static IPokemon CreatePokemon(string? abilityName)
private static IBattlePokemon CreatePokemon(string? abilityName)
{
var pokemon = Substitute.For<IPokemon>();
var pokemon = Substitute.For<IBattlePokemon>();
if (abilityName != null)
{
var ability = Substitute.For<IAbility>();
@@ -68,7 +66,7 @@ public class GearUpTests
/// null when that stat was not boosted. Received-call inspection is used instead of NSubstitute
/// argument matchers because the trailing EventBatchId parameter cannot be bound by <c>Arg.Any</c>.
/// </summary>
private static object?[]? GetStatBoostArgs(IPokemon pokemon, Statistic stat) =>
private static object?[]? GetStatBoostArgs(IBattlePokemon pokemon, Statistic stat) =>
pokemon.ReceivedCalls().Where(c => c.GetMethodInfo().Name == "ChangeStatBoost").Select(c => c.GetArguments())
.FirstOrDefault(args => (Statistic)args[0]! == stat);
@@ -81,10 +79,10 @@ public class GearUpTests
{
// Arrange
var ally = CreatePokemon("plus");
var (script, move, _) = CreateTestSetup(new List<IPokemon?> { ally });
var (script, move, _) = CreateTestSetup(new List<IBattlePokemon?> { ally });
// Act
script.OnSecondaryEffect(move, Substitute.For<IPokemon>(), 0);
script.OnSecondaryEffect(move, Substitute.For<IBattlePokemon>(), 0);
// Assert
var attackArgs = GetStatBoostArgs(ally, Statistic.Attack);
@@ -104,10 +102,10 @@ public class GearUpTests
{
// Arrange
var ally = CreatePokemon("minus");
var (script, move, _) = CreateTestSetup(new List<IPokemon?> { ally });
var (script, move, _) = CreateTestSetup(new List<IBattlePokemon?> { ally });
// Act
script.OnSecondaryEffect(move, Substitute.For<IPokemon>(), 0);
script.OnSecondaryEffect(move, Substitute.For<IBattlePokemon>(), 0);
// Assert
await Assert.That(GetStatBoostArgs(ally, Statistic.Attack)).IsNotNull();
@@ -127,11 +125,11 @@ public class GearUpTests
ability.Name.Returns(new StringKey("plus"));
user.ActiveAbility.Returns(ability);
var side = Substitute.For<IBattleSide>();
side.Pokemon.Returns(new List<IPokemon?> { user });
user.BattleData!.Battle.Sides.Returns(new[] { side });
side.Pokemon.Returns(new List<IBattlePokemon?> { user });
user.Battle.Sides.Returns(new[] { side });
// Act
script.OnSecondaryEffect(move, Substitute.For<IPokemon>(), 0);
script.OnSecondaryEffect(move, Substitute.For<IBattlePokemon>(), 0);
// Assert
var attackArgs = GetStatBoostArgs(user, Statistic.Attack);
@@ -148,10 +146,10 @@ public class GearUpTests
{
// Arrange
var ally = CreatePokemon("levitate");
var (script, move, _) = CreateTestSetup(new List<IPokemon?> { ally });
var (script, move, _) = CreateTestSetup(new List<IBattlePokemon?> { ally });
// Act
script.OnSecondaryEffect(move, Substitute.For<IPokemon>(), 0);
script.OnSecondaryEffect(move, Substitute.For<IBattlePokemon>(), 0);
// Assert
await Assert.That(ally.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "ChangeStatBoost")).IsFalse();
@@ -165,10 +163,10 @@ public class GearUpTests
{
// Arrange
var opponent = CreatePokemon("plus");
var (script, move, _) = CreateTestSetup(new List<IPokemon?>(), new List<IPokemon?> { opponent });
var (script, move, _) = CreateTestSetup(new List<IBattlePokemon?>(), new List<IBattlePokemon?> { opponent });
// Act
script.OnSecondaryEffect(move, Substitute.For<IPokemon>(), 0);
script.OnSecondaryEffect(move, Substitute.For<IBattlePokemon>(), 0);
// Assert
await Assert.That(opponent.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "ChangeStatBoost")).IsFalse();
@@ -182,10 +180,10 @@ public class GearUpTests
{
// Arrange
var ally = CreatePokemon(null);
var (script, move, _) = CreateTestSetup(new List<IPokemon?> { ally });
var (script, move, _) = CreateTestSetup(new List<IBattlePokemon?> { ally });
// Act
script.OnSecondaryEffect(move, Substitute.For<IPokemon>(), 0);
script.OnSecondaryEffect(move, Substitute.For<IBattlePokemon>(), 0);
// Assert
await Assert.That(ally.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "ChangeStatBoost")).IsFalse();
@@ -199,31 +197,12 @@ public class GearUpTests
{
// Arrange
var ally = CreatePokemon("plus");
var (script, move, _) = CreateTestSetup(new List<IPokemon?> { null, ally });
var (script, move, _) = CreateTestSetup(new List<IBattlePokemon?> { null, ally });
// Act
script.OnSecondaryEffect(move, Substitute.For<IPokemon>(), 0);
script.OnSecondaryEffect(move, Substitute.For<IBattlePokemon>(), 0);
// Assert
await Assert.That(GetStatBoostArgs(ally, Statistic.Attack)).IsNotNull();
}
/// <summary>
/// Technical test: when the user has no <see cref="IPokemon.BattleData"/> (it is not in a battle),
/// the script does nothing instead of throwing.
/// </summary>
[Test]
public async Task OnSecondaryEffect_NullBattleData_DoesNothing()
{
// Arrange
var ally = CreatePokemon("plus");
var (script, move, user) = CreateTestSetup(new List<IPokemon?> { ally });
user.BattleData.Returns((IPokemonBattleData?)null);
// Act
script.OnSecondaryEffect(move, Substitute.For<IPokemon>(), 0);
// Assert
await Assert.That(ally.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "ChangeStatBoost")).IsFalse();
}
}

View File

@@ -18,12 +18,10 @@ public class GenesisSupernovaTests
var move = Substitute.For<IExecutingMove>();
var battle = Substitute.For<IBattle>();
var battleData = Substitute.For<IPokemonBattleData>();
battleData.Battle.Returns(battle);
var user = Substitute.For<IPokemon>();
user.BattleData.Returns(battleData);
var user = Substitute.For<IBattlePokemon>();
move.User.Returns(user);
user.Battle.Returns(battle);
return (script, move, battle);
}
@@ -39,27 +37,9 @@ public class GenesisSupernovaTests
var (script, move, battle) = CreateTestSetup();
// Act
script.OnSecondaryEffect(move, Substitute.For<IPokemon>(), 0);
script.OnSecondaryEffect(move, Substitute.For<IBattlePokemon>(), 0);
// Assert
battle.Received(1).SetTerrain(ScriptUtils.ResolveName<PsychicTerrainScript>());
}
/// <summary>
/// Technical test: without battle data on the user (outside of battle) the effect does nothing and
/// does not throw.
/// </summary>
[Test]
public void OnSecondaryEffect_UserHasNoBattleData_DoesNotSetTerrain()
{
// Arrange
var (script, move, battle) = CreateTestSetup();
move.User.BattleData.Returns((IPokemonBattleData?)null);
// Act
script.OnSecondaryEffect(move, Substitute.For<IPokemon>(), 0);
// Assert
battle.DidNotReceiveWithAnyArgs().SetTerrain(default);
}
}

View File

@@ -15,11 +15,11 @@ namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves;
/// </summary>
public class GeomancyTests
{
private static (Geomancy script, IExecutingMove move, IPokemon user, ScriptSet userVolatile) CreateTestSetup()
private static (Geomancy script, IExecutingMove move, IBattlePokemon user, ScriptSet userVolatile) CreateTestSetup()
{
var script = new Geomancy();
var move = Substitute.For<IExecutingMove>();
var user = Substitute.For<IPokemon>();
var user = Substitute.For<IBattlePokemon>();
// Use a real script set so the charge volatile added by Geomancy can be inspected afterwards.
var userVolatile = new ScriptSet(user);
user.Volatile.Returns(userVolatile);
@@ -31,9 +31,7 @@ public class GeomancyTests
var battle = Substitute.For<IBattle>();
battle.EventHook.Returns(new EventHook());
var battleData = Substitute.For<IPokemonBattleData>();
battleData.Battle.Returns(battle);
user.BattleData.Returns(battleData);
user.Battle.Returns(battle);
return (script, move, user, userVolatile);
}
@@ -44,7 +42,7 @@ public class GeomancyTests
/// 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) =>
private static object?[]? GetStatBoostArgs(IBattlePokemon pokemon, Statistic stat) =>
pokemon.ReceivedCalls().Where(c => c.GetMethodInfo().Name == "ChangeStatBoost").Select(c => c.GetArguments())
.FirstOrDefault(args => (Statistic)args[0]! == stat);
@@ -114,7 +112,7 @@ public class GeomancyTests
var (script, move, user, _) = CreateTestSetup();
// Act
script.OnSecondaryEffect(move, Substitute.For<IPokemon>(), 0);
script.OnSecondaryEffect(move, Substitute.For<IBattlePokemon>(), 0);
// Assert
var args = GetStatBoostArgs(user, stat);
@@ -135,7 +133,7 @@ public class GeomancyTests
var (script, move, user, _) = CreateTestSetup();
// Act
script.OnSecondaryEffect(move, Substitute.For<IPokemon>(), 0);
script.OnSecondaryEffect(move, Substitute.For<IBattlePokemon>(), 0);
// Assert
await Assert.That(GetStatBoostArgs(user, Statistic.Attack)).IsNull();

View File

@@ -24,7 +24,7 @@ public class GrassKnotTests
// Arrange
var script = new GrassKnot();
var move = Substitute.For<IExecutingMove>();
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
target.WeightInKg.Returns(weightInKg);
ushort basePower = 1;
@@ -45,7 +45,7 @@ public class GrassKnotTests
// Arrange
var script = new GrassKnot();
var move = Substitute.For<IExecutingMove>();
var target = Substitute.For<IPokemon>();
var target = Substitute.For<IBattlePokemon>();
target.WeightInKg.Returns(5f);
ushort basePower = 250;

View File

@@ -26,10 +26,8 @@ public class GrassPledgeTests
var choice = Substitute.For<IMoveChoice>();
choice.GetScripts().Returns(_ => new ScriptIterator(new List<IEnumerable<ScriptContainer>>()));
var pokemon = Substitute.For<IPokemon>();
var battleData = Substitute.For<IPokemonBattleData>();
battleData.SideIndex.Returns(sideIndex);
pokemon.BattleData.Returns(battleData);
var pokemon = Substitute.For<IBattlePokemon>();
pokemon.SideIndex.Returns(sideIndex);
choice.User.Returns(pokemon);
var moveData = Substitute.For<IMoveData>();
@@ -56,10 +54,8 @@ public class GrassPledgeTests
battle.ChoiceQueue.Returns(queue);
move.Battle.Returns(battle);
var user = Substitute.For<IPokemon>();
var userBattleData = Substitute.For<IPokemonBattleData>();
userBattleData.SideIndex.Returns((byte)0);
user.BattleData.Returns(userBattleData);
var user = Substitute.For<IBattlePokemon>();
user.SideIndex.Returns((byte)0);
move.User.Returns(user);
var ownChoice = CreateQueuedChoice("grass_pledge", 0);

View File

@@ -18,12 +18,10 @@ public class GrassyTerrainTests
var move = Substitute.For<IExecutingMove>();
var battle = Substitute.For<IBattle>();
var battleData = Substitute.For<IPokemonBattleData>();
battleData.Battle.Returns(battle);
var user = Substitute.For<IPokemon>();
user.BattleData.Returns(battleData);
var user = Substitute.For<IBattlePokemon>();
move.User.Returns(user);
user.Battle.Returns(battle);
return (script, move, battle);
}
@@ -40,27 +38,9 @@ public class GrassyTerrainTests
var (script, move, battle) = CreateTestSetup();
// Act
script.OnSecondaryEffect(move, Substitute.For<IPokemon>(), 0);
script.OnSecondaryEffect(move, Substitute.For<IBattlePokemon>(), 0);
// Assert
battle.Received(1).SetTerrain(ScriptUtils.ResolveName<GrassyTerrainScript>());
}
/// <summary>
/// Technical test: without battle data on the user (outside of battle) the effect does nothing and
/// does not throw.
/// </summary>
[Test]
public void OnSecondaryEffect_UserHasNoBattleData_DoesNotSetTerrain()
{
// Arrange
var (script, move, battle) = CreateTestSetup();
move.User.BattleData.Returns((IPokemonBattleData?)null);
// Act
script.OnSecondaryEffect(move, Substitute.For<IPokemon>(), 0);
// Assert
battle.DidNotReceiveWithAnyArgs().SetTerrain(default);
}
}

View File

@@ -19,12 +19,12 @@ public class GravityTests
/// Creates a fully mocked test setup for Gravity tests. The battle's volatile script set is a
/// substitute so the addition of the battle-wide gravity script can be inspected.
/// </summary>
private static (Gravity script, IExecutingMove move, IPokemon target, IBattle battle, IScriptSet battleVolatile)
CreateTestSetup(params IReadOnlyList<IPokemon?>[] sidePokemon)
private static (Gravity script, IExecutingMove move, IBattlePokemon target, IBattle battle, IScriptSet
battleVolatile) CreateTestSetup(params IReadOnlyList<IBattlePokemon?>[] sidePokemon)
{
var script = new Gravity();
var move = Substitute.For<IExecutingMove>();
move.User.Returns(Substitute.For<IPokemon>());
move.User.Returns(Substitute.For<IBattlePokemon>());
var battle = Substitute.For<IBattle>();
var battleVolatile = Substitute.For<IScriptSet>();
@@ -37,11 +37,9 @@ public class GravityTests
}).ToArray();
battle.Sides.Returns(sides);
var battleData = Substitute.For<IPokemonBattleData>();
battleData.Battle.Returns(battle);
var target = Substitute.For<IPokemon>();
target.BattleData.Returns(battleData);
var target = Substitute.For<IBattlePokemon>();
target.Battle.Returns(battle);
return (script, move, target, battle, battleVolatile);
}
@@ -49,9 +47,9 @@ public class GravityTests
/// Creates a mocked Pokémon with a real <see cref="ScriptSet"/> as its volatile set, so effects can
/// be attached and their removal inspected.
/// </summary>
private static (IPokemon pokemon, ScriptSet volatileSet) CreateFieldPokemon()
private static (IBattlePokemon pokemon, ScriptSet volatileSet) CreateFieldPokemon()
{
var pokemon = Substitute.For<IPokemon>();
var pokemon = Substitute.For<IBattlePokemon>();
pokemon.GetScripts().Returns(_ => new ScriptIterator(new List<IEnumerable<ScriptContainer>>()));
var volatileSet = new ScriptSet(pokemon);
pokemon.Volatile.Returns(volatileSet);
@@ -106,7 +104,7 @@ public class GravityTests
// Arrange
var (pokemon, volatileSet) = CreateFieldPokemon();
volatileSet.Add(new ChargeFlyEffect(pokemon));
var (script, move, target, _, _) = CreateTestSetup(new List<IPokemon?> { pokemon });
var (script, move, target, _, _) = CreateTestSetup(new List<IBattlePokemon?> { pokemon });
// Act
script.OnSecondaryEffect(move, target, 0);
@@ -125,7 +123,7 @@ public class GravityTests
// Arrange
var (pokemon, volatileSet) = CreateFieldPokemon();
volatileSet.Add(new ChargeBounceEffect(pokemon));
var (script, move, target, _, _) = CreateTestSetup(new List<IPokemon?> { pokemon });
var (script, move, target, _, _) = CreateTestSetup(new List<IBattlePokemon?> { pokemon });
// Act
script.OnSecondaryEffect(move, target, 0);
@@ -145,7 +143,7 @@ public class GravityTests
// Arrange
var (pokemon, volatileSet) = CreateFieldPokemon();
volatileSet.Add(new ChargeSkyDropEffect(pokemon));
var (script, move, target, _, _) = CreateTestSetup(new List<IPokemon?> { pokemon });
var (script, move, target, _, _) = CreateTestSetup(new List<IBattlePokemon?> { pokemon });
// Act
script.OnSecondaryEffect(move, target, 0);
@@ -164,7 +162,7 @@ public class GravityTests
// Arrange
var (pokemon, volatileSet) = CreateFieldPokemon();
volatileSet.Add(new TelekinesisEffect());
var (script, move, target, _, _) = CreateTestSetup(new List<IPokemon?> { pokemon });
var (script, move, target, _, _) = CreateTestSetup(new List<IBattlePokemon?> { pokemon });
// Act
script.OnSecondaryEffect(move, target, 0);
@@ -183,7 +181,7 @@ public class GravityTests
// Arrange
var (pokemon, volatileSet) = CreateFieldPokemon();
volatileSet.Add(new MagnetRiseEffect());
var (script, move, target, _, _) = CreateTestSetup(new List<IPokemon?> { pokemon });
var (script, move, target, _, _) = CreateTestSetup(new List<IBattlePokemon?> { pokemon });
// Act
script.OnSecondaryEffect(move, target, 0);
@@ -204,8 +202,8 @@ public class GravityTests
allyVolatile.Add(new ChargeFlyEffect(allyPokemon));
var (opposingPokemon, opposingVolatile) = CreateFieldPokemon();
opposingVolatile.Add(new ChargeFlyEffect(opposingPokemon));
var (script, move, target, _, _) = CreateTestSetup(new List<IPokemon?> { allyPokemon },
new List<IPokemon?> { opposingPokemon });
var (script, move, target, _, _) = CreateTestSetup(new List<IBattlePokemon?> { allyPokemon },
new List<IBattlePokemon?> { opposingPokemon });
// Act
script.OnSecondaryEffect(move, target, 0);
@@ -222,7 +220,7 @@ public class GravityTests
public async Task OnSecondaryEffect_EmptyPokemonSlot_IsSkipped()
{
// Arrange
var (script, move, target, _, battleVolatile) = CreateTestSetup(new List<IPokemon?> { null });
var (script, move, target, _, battleVolatile) = CreateTestSetup(new List<IBattlePokemon?> { null });
// Act
script.OnSecondaryEffect(move, target, 0);
@@ -230,22 +228,4 @@ public class GravityTests
// Assert
await Assert.That(battleVolatile.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "StackOrAdd")).IsTrue();
}
/// <summary>
/// Technical test: when the target has no <see cref="IPokemon.BattleData"/> (it is not in a battle),
/// the script does nothing instead of throwing.
/// </summary>
[Test]
public async Task OnSecondaryEffect_NullBattleData_DoesNothing()
{
// Arrange
var (script, move, target, _, battleVolatile) = CreateTestSetup();
target.BattleData.Returns((IPokemonBattleData?)null);
// Act
script.OnSecondaryEffect(move, target, 0);
// Assert
await Assert.That(battleVolatile.ReceivedCalls().Any()).IsFalse();
}
}

Some files were not shown because too many files have changed in this diff Show More