fixed tests methinks + removed whirlwind test file

This commit is contained in:
Bertie690 2025-08-03 19:43:45 -04:00
parent 1da7637ab3
commit a22a6b89b9
3 changed files with 48 additions and 301 deletions

View File

@ -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);
});

View File

@ -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 },
])(

View File

@ -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);
});
});