From a22a6b89b957c9b60f1e7e61449a64f2a090ebf7 Mon Sep 17 00:00:00 2001 From: Bertie690 Date: Sun, 3 Aug 2025 19:43:45 -0400 Subject: [PATCH] fixed tests methinks + removed whirlwind test file --- test/abilities/wimp-out.test.ts | 4 +- test/moves/force-switch.test.ts | 93 ++++++------ test/moves/whirlwind.test.ts | 252 -------------------------------- 3 files changed, 48 insertions(+), 301 deletions(-) delete mode 100644 test/moves/whirlwind.test.ts diff --git a/test/abilities/wimp-out.test.ts b/test/abilities/wimp-out.test.ts index b108b4a1dff..bea05ee1998 100644 --- a/test/abilities/wimp-out.test.ts +++ b/test/abilities/wimp-out.test.ts @@ -59,7 +59,7 @@ describe("Abilities - Wimp Out/Emergency Exit", () => { expect(pokemon2.species.speciesId).not.toBe(SpeciesId.WIMPOD); expect(pokemon1.species.speciesId).toBe(SpeciesId.WIMPOD); - expect(pokemon1).toBeFainted(); + expect(pokemon1).toHaveFainted(); expect(pokemon1.getHpRatio()).toBeLessThan(0.5); } @@ -79,7 +79,7 @@ describe("Abilities - Wimp Out/Emergency Exit", () => { // Wimpod switched out after taking a hit, canceling its upcoming MoveEffectPhase before it could attack confirmSwitch(); - expect(game.field.getEnemyPokemon().).toHaveFullHp(); + expect(game.field.getEnemyPokemon()).toHaveFullHp(); expect(game.phaseInterceptor.log.filter(phase => phase === "MoveEffectPhase")).toHaveLength(1); }); diff --git a/test/moves/force-switch.test.ts b/test/moves/force-switch.test.ts index 31fc82592c7..05445861d0a 100644 --- a/test/moves/force-switch.test.ts +++ b/test/moves/force-switch.test.ts @@ -14,7 +14,7 @@ import { SpeciesId } from "#enums/species-id"; import { Stat } from "#enums/stat"; import { TrainerSlot } from "#enums/trainer-slot"; import { TrainerType } from "#enums/trainer-type"; -import { GameManager } from "#test/testUtils/gameManager"; +import { GameManager } from "#test/test-utils/game-manager"; import i18next from "i18next"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; @@ -46,6 +46,29 @@ describe("Moves - Switching Moves", () => { }); describe("Force Switch Moves", () => { + it.each<{ name: string; move: MoveId }>([ + { name: "Whirlwind", move: MoveId.WHIRLWIND }, + { name: "Roar", move: MoveId.ROAR }, + { name: "Dragon Tail", move: MoveId.DRAGON_TAIL }, + { name: "Circle Throw", move: MoveId.CIRCLE_THROW }, + ])("$name should switch the target out and display custom text", async ({ move }) => { + game.override.battleType(BattleType.TRAINER); + await game.classicMode.startBattle([SpeciesId.BLISSEY, SpeciesId.BULBASAUR]); + + const enemy = game.field.getEnemyPokemon(); + game.move.use(move); + await game.toNextTurn(); + + const newEnemy = game.field.getEnemyPokemon(); + expect(newEnemy).not.toBe(enemy); + expect(game.phaseInterceptor.log).toContain("SwitchSummonPhase"); + expect(game.textInterceptor.logs).toContain( + i18next.t("battle:pokemonDraggedOut", { + pokemonName: getPokemonNameWithAffix(newEnemy), + }), + ); + }); + it("should force switches to a random off-field pokemon", async () => { await game.classicMode.startBattle([SpeciesId.BULBASAUR, SpeciesId.CHARMANDER, SpeciesId.SQUIRTLE]); @@ -62,7 +85,7 @@ describe("Moves - Switching Moves", () => { expect(bulbasaur.isOnField()).toBe(false); expect(charmander.isOnField()).toBe(true); expect(squirtle.isOnField()).toBe(false); - expect(bulbasaur.getInverseHp()).toBeGreaterThan(0); + expect(bulbasaur).not.toHaveFullHp(); // Turn 2: Mock an RNG call that calls for switching to 2nd backup Pokemon (Squirtle) vi.spyOn(game.scene, "randBattleSeedInt").mockImplementation((_range, min = 0) => { @@ -74,7 +97,7 @@ describe("Moves - Switching Moves", () => { expect(bulbasaur.isOnField()).toBe(false); expect(charmander.isOnField()).toBe(false); expect(squirtle.isOnField()).toBe(true); - expect(charmander.getInverseHp()).toBeGreaterThan(0); + expect(charmander).not.toHaveFullHp(); }); it("should force trainers to switch randomly without selecting from a partner's party", async () => { @@ -131,7 +154,7 @@ describe("Moves - Switching Moves", () => { expect(enemyLeadPokemon.visible).toBe(false); expect(enemyLeadPokemon.switchOutStatus).toBe(true); - expect(enemySecPokemon.hp).toBeLessThan(enemySecPokemon.getMaxHp()); + expect(enemySecPokemon).not.toHaveFullHp(); }); it("should not switch out a target with suction cups, unless the user has Mold Breaker", async () => { @@ -144,7 +167,7 @@ describe("Moves - Switching Moves", () => { await game.toEndOfTurn(); expect(enemy.isOnField()).toBe(true); - expect(enemy.isFullHp()).toBe(false); + expect(enemy).not.toHaveFullHp(); // Turn 2: Mold Breaker should ignore switch blocking ability and switch out the target game.field.mockAbility(game.field.getPlayerPokemon(), AbilityId.MOLD_BREAKER); @@ -154,7 +177,7 @@ describe("Moves - Switching Moves", () => { await game.toEndOfTurn(); expect(enemy.isOnField()).toBe(false); - expect(enemy.isFullHp()).toBe(false); + expect(enemy).not.toHaveFullHp(); }); it("should not switch out a Commanded Dondozo", async () => { @@ -169,7 +192,7 @@ describe("Moves - Switching Moves", () => { await game.toEndOfTurn(); expect(dondozo1.isOnField()).toBe(true); - expect(dondozo1.isFullHp()).toBe(false); + expect(dondozo1).not.toHaveFullHp(); }); it("should perform a normal switch upon fainting an opponent", async () => { @@ -184,7 +207,7 @@ describe("Moves - Switching Moves", () => { const enemy = game.field.getEnemyPokemon(); expect(enemy).toBeDefined(); - expect(enemy.isFullHp()).toBe(true); + expect(enemy).toHaveFullHp(); expect(choiceSwitchSpy).toHaveBeenCalledTimes(1); }); @@ -249,31 +272,7 @@ describe("Moves - Switching Moves", () => { expect(eevee.isOnField()).toBe(false); expect(toxapex.isOnField()).toBe(false); expect(primarina.isOnField()).toBe(true); - expect(lapras.getInverseHp()).toBeGreaterThan(0); - }); - - it.each<{ name: string; move: MoveId }>([ - { name: "Whirlwind", move: MoveId.WHIRLWIND }, - { name: "Roar", move: MoveId.ROAR }, - { name: "Dragon Tail", move: MoveId.DRAGON_TAIL }, - { name: "Circle Throw", move: MoveId.CIRCLE_THROW }, - ])("should display custom text for forced switch outs", async ({ move }) => { - game.override.battleType(BattleType.TRAINER); - await game.classicMode.startBattle([SpeciesId.BLISSEY, SpeciesId.BULBASAUR]); - - const enemy = game.field.getEnemyPokemon(); - game.move.use(move); - await game.toNextTurn(); - - const newEnemy = game.field.getEnemyPokemon(); - expect(newEnemy).not.toBe(enemy); - expect(game.phaseInterceptor.log).toContain("SwitchSummonPhase"); - // TODO: Replace this with the locale key in question - expect(game.textInterceptor.logs).toContain( - i18next.t("battle:pokemonDraggedOut", { - pokemonName: getPokemonNameWithAffix(newEnemy), - }), - ); + expect(lapras).not.toHaveFullHp(); }); }); @@ -292,7 +291,7 @@ describe("Moves - Switching Moves", () => { expect(game.phaseInterceptor.log).not.toContain("SwitchSummonPhase"); const player = game.field.getPlayerPokemon(); expect(player).toBe(raichu); - expect(player.isFullHp()).toBe(false); + expect(player).not.toHaveFullHp(); expect(game.field.getEnemyPokemon().waveData.abilityRevealed).toBe(true); // proxy for asserting ability activated }); }); @@ -305,20 +304,20 @@ describe("Moves - Switching Moves", () => { await game.toNextTurn(); const [raichu, shuckle] = game.scene.getPlayerParty(); - expect(raichu.getStatStage(Stat.SPATK)).toEqual(2); + expect(raichu).toHaveStatStage(Stat.SPATK, 2); game.move.use(MoveId.SUBSTITUTE); await game.toNextTurn(); - expect(raichu.getTag(BattlerTagType.SUBSTITUTE)).toBeDefined(); + expect(raichu).toHaveBattlerTag(BattlerTagType.SUBSTITUTE); game.move.use(MoveId.BATON_PASS); game.doSelectPartyPokemon(1); await game.toEndOfTurn(); expect(game.field.getPlayerPokemon()).toBe(shuckle); - expect(shuckle.getStatStage(Stat.SPATK)).toEqual(2); - expect(shuckle.getTag(BattlerTagType.SUBSTITUTE)).toBeDefined(); + expect(shuckle).toHaveStatStage(Stat.SPATK, 2); + expect(shuckle).toHaveBattlerTag(BattlerTagType.SUBSTITUTE); }); it("should not transfer non-transferrable effects", async () => { @@ -329,16 +328,16 @@ describe("Moves - Switching Moves", () => { game.move.use(MoveId.BATON_PASS); await game.move.forceEnemyMove(MoveId.SALT_CURE); await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); - await game.phaseInterceptor.to("MoveEndPhase"); - expect(player1.getTag(BattlerTagType.SALT_CURED)).toBeDefined(); + + expect(player1).toHaveBattlerTag(BattlerTagType.SALT_CURED); game.doSelectPartyPokemon(1); await game.toNextTurn(); expect(player1.isOnField()).toBe(false); expect(player2.isOnField()).toBe(true); - expect(player2.getTag(BattlerTagType.SALT_CURED)).toBeUndefined(); + expect(player2).not.toHaveBattlerTag(BattlerTagType.SALT_CURED); }); it("should remove the user's binding effects on end", async () => { @@ -349,13 +348,13 @@ describe("Moves - Switching Moves", () => { await game.toNextTurn(); const enemy = game.field.getEnemyPokemon(); - expect(enemy.getTag(BattlerTagType.FIRE_SPIN)).toBeDefined(); + expect(enemy).toHaveBattlerTag(BattlerTagType.FIRE_SPIN); game.move.use(MoveId.BATON_PASS); game.doSelectPartyPokemon(1); await game.toNextTurn(); - expect(enemy.getTag(BattlerTagType.FIRE_SPIN)).toBeUndefined(); + expect(enemy).not.toHaveBattlerTag(BattlerTagType.FIRE_SPIN); }); }); @@ -376,7 +375,7 @@ describe("Moves - Switching Moves", () => { const substituteTag = feebas.getTag(SubstituteTag)!; expect(substituteTag).toBeDefined(); - expect(magikarp.getInverseHp()).toBe(Math.ceil(magikarp.getMaxHp() / 2)); + expect(magikarp).toHaveTakenDamage(Math.ceil(magikarp.getMaxHp() / 2)); expect(substituteTag.hp).toBe(Math.floor(magikarp.getMaxHp() / 4)); }); @@ -407,8 +406,8 @@ describe("Moves - Switching Moves", () => { await game.toEndOfTurn(); expect(magikarp.isOnField()).toBe(true); - expect(magikarp.getLastXMoves()[0].result).toBe(MoveResult.FAIL); - expect(magikarp.hp).toBe(initHp); + expect(magikarp).toHaveUsedMove({ move: MoveId.SHED_TAIL, result: MoveResult.FAIL }); + expect(magikarp).toHaveHp(initHp); }); }); @@ -459,7 +458,7 @@ describe("Moves - Switching Moves", () => { { name: "Flip Turn", move: MoveId.FLIP_TURN }, { name: "Volt Switch", move: MoveId.VOLT_SWITCH }, // TODO: Enable once Parting shot is fixed - { name: "Parting Shot", move: MoveId.PARTING_SHOT }, + // { name: "Parting Shot", move: MoveId.PARTING_SHOT }, { name: "Dragon Tail", enemyMove: MoveId.DRAGON_TAIL }, { name: "Circle Throw", enemyMove: MoveId.CIRCLE_THROW }, ])( diff --git a/test/moves/whirlwind.test.ts b/test/moves/whirlwind.test.ts deleted file mode 100644 index 2aadb76b019..00000000000 --- a/test/moves/whirlwind.test.ts +++ /dev/null @@ -1,252 +0,0 @@ -import { globalScene } from "#app/global-scene"; -import { Status } from "#data/status-effect"; -import { AbilityId } from "#enums/ability-id"; -import { BattleType } from "#enums/battle-type"; -import { BattlerIndex } from "#enums/battler-index"; -import { BattlerTagType } from "#enums/battler-tag-type"; -import { Challenges } from "#enums/challenges"; -import { MoveId } from "#enums/move-id"; -import { MoveResult } from "#enums/move-result"; -import { PokemonType } from "#enums/pokemon-type"; -import { SpeciesId } from "#enums/species-id"; -import { StatusEffect } from "#enums/status-effect"; -import { TrainerType } from "#enums/trainer-type"; -import { GameManager } from "#test/test-utils/game-manager"; -import Phaser from "phaser"; -import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; - -describe("Moves - Whirlwind", () => { - let phaserGame: Phaser.Game; - let game: GameManager; - - beforeAll(() => { - phaserGame = new Phaser.Game({ - type: Phaser.HEADLESS, - }); - }); - - afterEach(() => { - game.phaseInterceptor.restoreOg(); - }); - - beforeEach(() => { - game = new GameManager(phaserGame); - game.override - .battleStyle("single") - .moveset([MoveId.SPLASH]) - .enemyAbility(AbilityId.BALL_FETCH) - .enemyMoveset([MoveId.SPLASH, MoveId.WHIRLWIND]) - .enemySpecies(SpeciesId.PIDGEY); - }); - - it.each([ - { move: MoveId.FLY, name: "Fly" }, - { move: MoveId.BOUNCE, name: "Bounce" }, - { move: MoveId.SKY_DROP, name: "Sky Drop" }, - ])("should not hit a flying target: $name (=$move)", async ({ move }) => { - game.override.moveset([move]); - // Must have a pokemon in the back so that the move misses instead of fails. - await game.classicMode.startBattle([SpeciesId.STARAPTOR, SpeciesId.MAGIKARP]); - - const staraptor = game.scene.getPlayerPokemon()!; - - game.move.select(move); - await game.move.selectEnemyMove(MoveId.WHIRLWIND); - - await game.phaseInterceptor.to("BerryPhase", false); - - expect(staraptor.findTag(t => t.tagType === BattlerTagType.FLYING)).toBeDefined(); - expect(game.scene.getEnemyPokemon()!.getLastXMoves(1)[0].result).toBe(MoveResult.MISS); - }); - - it("should force switches randomly", async () => { - await game.classicMode.startBattle([SpeciesId.BULBASAUR, SpeciesId.CHARMANDER, SpeciesId.SQUIRTLE]); - - const [bulbasaur, charmander, squirtle] = game.scene.getPlayerParty(); - - // Turn 1: Mock an RNG call that calls for switching to 1st backup Pokemon (Charmander) - vi.spyOn(game.scene, "randBattleSeedInt").mockImplementation((_range, min = 0) => { - return min; - }); - game.move.select(MoveId.SPLASH); - await game.move.selectEnemyMove(MoveId.WHIRLWIND); - await game.toNextTurn(); - - expect(bulbasaur.isOnField()).toBe(false); - expect(charmander.isOnField()).toBe(true); - expect(squirtle.isOnField()).toBe(false); - - // Turn 2: Mock an RNG call that calls for switching to 2nd backup Pokemon (Squirtle) - vi.spyOn(game.scene, "randBattleSeedInt").mockImplementation((_range, min = 0) => { - return min + 1; - }); - game.move.select(MoveId.SPLASH); - await game.move.selectEnemyMove(MoveId.WHIRLWIND); - await game.toNextTurn(); - - expect(bulbasaur.isOnField()).toBe(false); - expect(charmander.isOnField()).toBe(false); - expect(squirtle.isOnField()).toBe(true); - }); - - it("should not force a switch to a challenge-ineligible Pokemon", async () => { - // Mono-Water challenge, Eevee is ineligible - game.challengeMode.addChallenge(Challenges.SINGLE_TYPE, PokemonType.WATER + 1, 0); - await game.challengeMode.startBattle([SpeciesId.LAPRAS, SpeciesId.EEVEE, SpeciesId.TOXAPEX, SpeciesId.PRIMARINA]); - - const [lapras, eevee, toxapex, primarina] = game.scene.getPlayerParty(); - - // Turn 1: Mock an RNG call that would normally call for switching to Eevee, but it is ineligible - vi.spyOn(game.scene, "randBattleSeedInt").mockImplementation((_range, min = 0) => { - return min; - }); - game.move.select(MoveId.SPLASH); - await game.move.selectEnemyMove(MoveId.WHIRLWIND); - await game.toNextTurn(); - - expect(lapras.isOnField()).toBe(false); - expect(eevee.isOnField()).toBe(false); - expect(toxapex.isOnField()).toBe(true); - expect(primarina.isOnField()).toBe(false); - }); - - it("should not force a switch to a fainted Pokemon", async () => { - await game.classicMode.startBattle([SpeciesId.LAPRAS, SpeciesId.EEVEE, SpeciesId.TOXAPEX, SpeciesId.PRIMARINA]); - - const [lapras, eevee, toxapex, primarina] = game.scene.getPlayerParty(); - - // Turn 1: Eevee faints - eevee.hp = 0; - eevee.status = new Status(StatusEffect.FAINT); - expect(eevee.isFainted()).toBe(true); - game.move.select(MoveId.SPLASH); - await game.move.selectEnemyMove(MoveId.SPLASH); - await game.toNextTurn(); - - // Turn 2: Mock an RNG call that would normally call for switching to Eevee, but it is fainted - vi.spyOn(game.scene, "randBattleSeedInt").mockImplementation((_range, min = 0) => { - return min; - }); - game.move.select(MoveId.SPLASH); - await game.move.selectEnemyMove(MoveId.WHIRLWIND); - await game.toNextTurn(); - - expect(lapras.isOnField()).toBe(false); - expect(eevee.isOnField()).toBe(false); - expect(toxapex.isOnField()).toBe(true); - expect(primarina.isOnField()).toBe(false); - }); - - it("should not force a switch if there are no available Pokemon to switch into", async () => { - await game.classicMode.startBattle([SpeciesId.LAPRAS, SpeciesId.EEVEE]); - - const [lapras, eevee] = game.scene.getPlayerParty(); - - // Turn 1: Eevee faints - eevee.hp = 0; - eevee.status = new Status(StatusEffect.FAINT); - expect(eevee.isFainted()).toBe(true); - game.move.select(MoveId.SPLASH); - await game.move.selectEnemyMove(MoveId.SPLASH); - await game.toNextTurn(); - - // Turn 2: Mock an RNG call that would normally call for switching to Eevee, but it is fainted - vi.spyOn(game.scene, "randBattleSeedInt").mockImplementation((_range, min = 0) => { - return min; - }); - game.move.select(MoveId.SPLASH); - await game.move.selectEnemyMove(MoveId.WHIRLWIND); - await game.toNextTurn(); - - expect(lapras.isOnField()).toBe(true); - expect(eevee.isOnField()).toBe(false); - }); - - it("should fail when player uses Whirlwind against an opponent with only one available Pokémon", async () => { - // Set up the battle scenario with the player knowing Whirlwind - game.override.startingWave(5).enemySpecies(SpeciesId.PIDGEY).moveset([MoveId.WHIRLWIND]); - await game.classicMode.startBattle(); - - const enemyParty = game.scene.getEnemyParty(); - - // Ensure the opponent has only one available Pokémon - if (enemyParty.length > 1) { - enemyParty.slice(1).forEach(p => { - p.hp = 0; - p.status = new Status(StatusEffect.FAINT); - }); - } - const eligibleEnemy = enemyParty.filter(p => p.hp > 0 && p.isAllowedInBattle()); - expect(eligibleEnemy.length).toBe(1); - - // Spy on the queueMessage function - const queueSpy = vi.spyOn(globalScene.phaseManager, "queueMessage"); - - // Player uses Whirlwind; opponent uses Splash - game.move.select(MoveId.WHIRLWIND); - await game.move.selectEnemyMove(MoveId.SPLASH); - await game.toNextTurn(); - - // Verify that the failure message is displayed for Whirlwind - expect(queueSpy).toHaveBeenCalledWith(expect.stringContaining("But it failed")); - // Verify the opponent's Splash message - expect(queueSpy).toHaveBeenCalledWith(expect.stringContaining("But nothing happened!")); - }); - - it("should not pull in the other trainer's pokemon in a partner trainer battle", async () => { - game.override - .startingWave(2) - .battleType(BattleType.TRAINER) - .randomTrainer({ - trainerType: TrainerType.BREEDER, - alwaysDouble: true, - }) - .enemyMoveset([MoveId.SPLASH, MoveId.LUNAR_DANCE]) - .moveset([MoveId.WHIRLWIND, MoveId.SPLASH]); - await game.classicMode.startBattle([SpeciesId.MAGIKARP, SpeciesId.TOTODILE]); - - // expect the enemy to have at least 4 pokemon, necessary for this check to even work - expect(game.scene.getEnemyParty().length, "enemy must have exactly 4 pokemon").toBeGreaterThanOrEqual(4); - - const user = game.scene.getPlayerPokemon()!; - - console.log(user.getMoveset(false)); - - game.move.select(MoveId.SPLASH); - game.move.select(MoveId.SPLASH); - await game.move.selectEnemyMove(MoveId.MEMENTO); - await game.move.selectEnemyMove(MoveId.SPLASH); - await game.toNextTurn(); - - // Get the enemy pokemon id so we can check if is the same after switch. - const enemy_id = game.scene.getEnemyPokemon()!.id; - - // Hit the enemy that fainted with whirlwind. - game.move.select(MoveId.WHIRLWIND, 0, BattlerIndex.ENEMY); - game.move.select(MoveId.SPLASH, 1); - - await game.move.selectEnemyMove(MoveId.SPLASH); - await game.move.selectEnemyMove(MoveId.SPLASH); - - await game.toNextTurn(); - - // Expect the enemy pokemon to not have switched out. - expect(game.scene.getEnemyPokemon()!.id).toBe(enemy_id); - }); - - it("should force a wild pokemon to flee", async () => { - game.override - .battleType(BattleType.WILD) - .moveset([MoveId.WHIRLWIND, MoveId.SPLASH]) - .enemyMoveset(MoveId.SPLASH) - .ability(AbilityId.BALL_FETCH); - await game.classicMode.startBattle([SpeciesId.MAGIKARP]); - - const user = game.scene.getPlayerPokemon()!; - - game.move.select(MoveId.WHIRLWIND); - await game.phaseInterceptor.to("BerryPhase"); - - expect(user.getLastXMoves(1)[0].result).toBe(MoveResult.SUCCESS); - }); -});