using PkmnLib.Dynamic.Libraries; using PkmnLib.Dynamic.Models; using PkmnLib.Dynamic.Models.Choices; using PkmnLib.Plugin.Gen7.Scripts.Moves; using PkmnLib.Static.Libraries; using PkmnLib.Static.Moves; using PkmnLib.Static.Utils; namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves; /// /// Tests for the script, which implements Metronome. /// Behavior is verified against the Bulbapedia page for Metronome (Generation VII). /// public class MetronomeTests { /// /// Creates a fully mocked test setup for Metronome tests. The user's move library and battle random are /// mocked so that tests control which move "randomly" rolls. /// private static (Metronome metronome, IMoveChoice choice, IPokemon user, IReadOnlyMoveLibrary moveLibrary, IBattleRandom random) CreateTestSetup() { var metronome = new Metronome(); var user = Substitute.For(); var choice = Substitute.For(); choice.User.Returns(user); var battle = Substitute.For(); var random = Substitute.For(); battle.Random.Returns(random); var battleData = Substitute.For(); battleData.Battle.Returns(battle); user.BattleData.Returns(battleData); var moveLibrary = Substitute.For(); var staticLibrary = Substitute.For(); staticLibrary.Moves.Returns(moveLibrary); var dynamicLibrary = Substitute.For(); dynamicLibrary.StaticLibrary.Returns(staticLibrary); user.Library.Returns(dynamicLibrary); return (metronome, choice, user, moveLibrary, random); } /// /// Creates a mocked with the given name. /// private static IMoveData CreateMoveData(string name) { var moveData = Substitute.For(); moveData.Name.Returns(new StringKey(name)); return moveData; } /// /// Bulbapedia: "The Metronome user executes a randomly selected move." /// The move rolled from the move library replaces the used move name. /// [Test] public async Task ChangeMove_SelectableMoveRolled_MoveNameChangesToRolledMove() { // Arrange var (metronome, choice, _, moveLibrary, random) = CreateTestSetup(); var tackle = CreateMoveData("tackle"); moveLibrary.GetRandom(random).Returns(tackle); var moveName = new StringKey("metronome"); // Act metronome.ChangeMove(choice, ref moveName); // Assert await Assert.That(moveName).IsEqualTo(new StringKey("tackle")); } /// /// Technical test: if the user has no , the script cannot roll a random /// move and must leave the move name unchanged. /// [Test] public async Task ChangeMove_UserHasNoBattleData_MoveNameUnchanged() { // Arrange var (metronome, choice, user, moveLibrary, random) = CreateTestSetup(); user.BattleData.Returns((IPokemonBattleData?)null); var tackle = CreateMoveData("tackle"); moveLibrary.GetRandom(random).Returns(tackle); var moveName = new StringKey("metronome"); // Act metronome.ChangeMove(choice, ref moveName); // Assert await Assert.That(moveName).IsEqualTo(new StringKey("metronome")); } /// /// Bulbapedia: "Metronome cannot select a Max Move, a Z-Move, or any of the moves" in its list of /// unselectable moves. When an unselectable move is rolled, the script rerolls until it finds a /// selectable one. /// [Test] public async Task ChangeMove_UnselectableMoveRolledFirst_RerollsUntilSelectableMove() { // Arrange var (metronome, choice, _, moveLibrary, random) = CreateTestSetup(); var protect = CreateMoveData("protect"); var tackle = CreateMoveData("tackle"); moveLibrary.GetRandom(random).Returns(protect, tackle); var moveName = new StringKey("metronome"); // Act metronome.ChangeMove(choice, ref moveName); // Assert await Assert.That(moveName).IsEqualTo(new StringKey("tackle")); } /// /// Bulbapedia: "Metronome cannot select a Max Move, a Z-Move, or any of the moves" in its list of /// unselectable moves. A sample of moves that are unselectable in Generation VII (including Metronome /// itself) is rolled first; the script must skip them and use the next (selectable) roll instead. /// [Test, Arguments("metronome"), Arguments("protect"), Arguments("detect"), Arguments("endure"), Arguments("counter"), Arguments("mirror_coat"), Arguments("mirror_move"), Arguments("mimic"), Arguments("sketch"), Arguments("sleep_talk"), Arguments("struggle"), Arguments("transform"), Arguments("destiny_bond"), Arguments("focus_punch"), Arguments("helping_hand"), Arguments("assist"), Arguments("copycat"), Arguments("chatter"), Arguments("after_you"), Arguments("quash"), Arguments("wide_guard"), Arguments("quick_guard"), Arguments("instruct"), Arguments("spectral_thief"), Arguments("crafty_shield"), Arguments("snore")] public async Task ChangeMove_Gen7UnselectableMoveRolled_IsNeverSelected(string unselectableMove) { // Arrange var (metronome, choice, _, moveLibrary, random) = CreateTestSetup(); var rolled = CreateMoveData(unselectableMove); var tackle = CreateMoveData("tackle"); moveLibrary.GetRandom(random).Returns(rolled, tackle); var moveName = new StringKey("metronome"); // Act metronome.ChangeMove(choice, ref moveName); // Assert - the unselectable move was skipped in favor of the next roll. await Assert.That(moveName).IsEqualTo(new StringKey("tackle")); } /// /// Bulbapedia: "The Metronome user executes a randomly selected move." Moves such as Dig, Fly, and Roar do /// not appear in the Generation VII column of Bulbapedia's list of moves Metronome cannot select, so /// Metronome must be able to call them (they are only excluded for other copying moves such as Assist). /// [Test, Arguments("dig"), Arguments("fly"), Arguments("roar")] public async Task ChangeMove_MoveSelectableByMetronomeButBannedForAssistRolled_IsSelected(string selectableMove) { // Arrange var (metronome, choice, _, moveLibrary, random) = CreateTestSetup(); var rolled = CreateMoveData(selectableMove); var tackle = CreateMoveData("tackle"); moveLibrary.GetRandom(random).Returns(rolled, tackle); var moveName = new StringKey("metronome"); // Act metronome.ChangeMove(choice, ref moveName); // Assert - the first roll is selectable by Metronome in Generation VII, so it must be used. await Assert.That(moveName).IsEqualTo(new StringKey(selectableMove)); } }