diff --git a/src/test/moves/u_turn.test.ts b/src/test/moves/u_turn.test.ts index ce8942a348c..84bd5eeeb4e 100644 --- a/src/test/moves/u_turn.test.ts +++ b/src/test/moves/u_turn.test.ts @@ -7,7 +7,6 @@ import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -import { BerryPhase } from "#app/phases/berry-phase.js"; describe("Moves - U-turn", () => { let phaserGame: Phaser.Game; @@ -36,40 +35,28 @@ describe("Moves - U-turn", () => { }); it("triggers regenerator a single time when a regenerator user switches out with u-turn", async () => { - // arrange const playerHp = 1; game.override.ability(Abilities.REGENERATOR); - await game.startBattle([ - Species.RAICHU, - Species.SHUCKLE - ]); + await game.classicMode.startBattle([Species.RAICHU, Species.SHUCKLE]); game.scene.getPlayerPokemon()!.hp = playerHp; - // act game.move.select(Moves.U_TURN); game.doSelectPartyPokemon(1); await game.phaseInterceptor.to(TurnEndPhase); - // assert expect(game.scene.getPlayerPokemon()!.species.speciesId).toBe(Species.SHUCKLE); expect(game.scene.getParty()[1].hp).toEqual(Math.floor(game.scene.getParty()[1].getMaxHp() * 0.33 + playerHp)); expect(game.phaseInterceptor.log).toContain("SwitchSummonPhase"); }, 20000); it("triggers rough skin on the u-turn user before a new pokemon is switched in", async () => { - // arrange game.override.enemyAbility(Abilities.ROUGH_SKIN); - await game.startBattle([ - Species.RAICHU, - Species.SHUCKLE - ]); + await game.classicMode.startBattle([Species.RAICHU, Species.SHUCKLE]); - // act game.move.select(Moves.U_TURN); game.doSelectPartyPokemon(1); await game.phaseInterceptor.to(SwitchPhase, false); - // assert const playerPkm = game.scene.getPlayerPokemon()!; expect(playerPkm.species.speciesId).toEqual(Species.RAICHU); expect(playerPkm.hp).not.toEqual(playerPkm.getMaxHp()); @@ -78,46 +65,17 @@ describe("Moves - U-turn", () => { }, 20000); it("triggers contact abilities on the u-turn user (eg poison point) before a new pokemon is switched in", async () => { - // arrange game.override.enemyAbility(Abilities.POISON_POINT); - await game.startBattle([ - Species.RAICHU, - Species.SHUCKLE - ]); + await game.classicMode.startBattle([Species.RAICHU, Species.SHUCKLE]); vi.spyOn(game.scene.getEnemyPokemon()!, "randSeedInt").mockReturnValue(0); - // act game.move.select(Moves.U_TURN); await game.phaseInterceptor.to(SwitchPhase, false); - // assert const playerPkm = game.scene.getPlayerPokemon()!; expect(playerPkm.species.speciesId).toEqual(Species.RAICHU); expect(playerPkm.status?.effect).toEqual(StatusEffect.POISON); expect(game.scene.getEnemyPokemon()!.battleData.abilityRevealed).toBe(true); // proxy for asserting ability activated expect(game.phaseInterceptor.log).not.toContain("SwitchSummonPhase"); }, 20000); - - it("does not switch out the user if the move fails", async () => { - // arrange - game.override - .enemySpecies(Species.DUGTRIO) - .moveset(Moves.VOLT_SWITCH); // cheating a little here but no types are immune to bug - await game.startBattle([ - Species.RAICHU, - Species.SHUCKLE - ]); - - // act - game.doAttack(getMovePosition(game.scene, 0, Moves.U_TURN)); - game.onNextPrompt("SwitchPhase", Mode.PARTY, () => { - expect.fail("Switch was forced"); - }, () => game.isCurrentPhase(BerryPhase)); - await game.phaseInterceptor.to(BerryPhase, false); - - // assert - const playerPkm = game.scene.getPlayerPokemon()!; - expect(playerPkm.species.speciesId).toEqual(Species.RAICHU); - expect(game.phaseInterceptor.log).not.toContain("SwitchSummonPhase"); - }, 20000); }); diff --git a/src/test/moves/volt_switch.test.ts b/src/test/moves/volt_switch.test.ts new file mode 100644 index 00000000000..6c137df260a --- /dev/null +++ b/src/test/moves/volt_switch.test.ts @@ -0,0 +1,50 @@ +import { BerryPhase } from "#app/phases/berry-phase"; +import { Mode } from "#app/ui/ui"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +describe("Moves - Volt Switch", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + const TIMEOUT = 20 * 1000; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .moveset([Moves.SPLASH]) + .battleType("single") + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset(Moves.SPLASH); + }); + + it("does not switch out the user if the move fails", async () => { + game.override + .enemySpecies(Species.DUGTRIO) + .moveset(Moves.VOLT_SWITCH); + await game.classicMode.startBattle([Species.RAICHU, Species.SHUCKLE]); + + game.move.select(Moves.VOLT_SWITCH); + game.onNextPrompt("SwitchPhase", Mode.PARTY, () => { + expect.fail("Switch was forced"); + }, () => game.isCurrentPhase(BerryPhase)); + await game.phaseInterceptor.to(BerryPhase, false); + + const playerPkm = game.scene.getPlayerPokemon()!; + expect(playerPkm.species.speciesId).toEqual(Species.RAICHU); + expect(game.phaseInterceptor.log).not.toContain("SwitchSummonPhase"); + }, TIMEOUT); +}); diff --git a/src/test/utils/gameManager.ts b/src/test/utils/gameManager.ts index 088875b090d..87f8cf00fff 100644 --- a/src/test/utils/gameManager.ts +++ b/src/test/utils/gameManager.ts @@ -1,20 +1,23 @@ import { updateUserInfo } from "#app/account"; import { BattlerIndex } from "#app/battle"; import BattleScene from "#app/battle-scene"; -import { BattleStyle } from "#app/enums/battle-style"; -import { Moves } from "#app/enums/moves"; import { getMoveTargets } from "#app/data/move"; -import { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; +import { BattleStyle } from "#app/enums/battle-style"; +import { ExpGainsSpeed } from "#app/enums/exp-gains-speed"; +import { Moves } from "#app/enums/moves"; +import Pokemon from "#app/field/pokemon"; import Trainer from "#app/field/trainer"; import { GameModes, getGameMode } from "#app/game-mode"; import { ModifierTypeOption, modifierTypes } from "#app/modifier/modifier-type"; import overrides from "#app/overrides"; +import { CheckSwitchPhase } from "#app/phases/check-switch-phase"; import { CommandPhase } from "#app/phases/command-phase"; import { EncounterPhase } from "#app/phases/encounter-phase"; import { EnemyCommandPhase } from "#app/phases/enemy-command-phase"; import { FaintPhase } from "#app/phases/faint-phase"; import { LoginPhase } from "#app/phases/login-phase"; import { MovePhase } from "#app/phases/move-phase"; +import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases"; import { NewBattlePhase } from "#app/phases/new-battle-phase"; import { SelectStarterPhase } from "#app/phases/select-starter-phase"; import { SelectTargetPhase } from "#app/phases/select-target-phase"; @@ -24,13 +27,16 @@ import { TurnInitPhase } from "#app/phases/turn-init-phase"; import { TurnStartPhase } from "#app/phases/turn-start-phase"; import ErrorInterceptor from "#app/test/utils/errorInterceptor"; import InputsHandler from "#app/test/utils/inputsHandler"; +import BattleMessageUiHandler from "#app/ui/battle-message-ui-handler"; import CommandUiHandler from "#app/ui/command-ui-handler"; import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; import PartyUiHandler from "#app/ui/party-ui-handler"; import TargetSelectUiHandler from "#app/ui/target-select-ui-handler"; import { Mode } from "#app/ui/ui"; +import { isNullOrUndefined } from "#app/utils"; import { Button } from "#enums/buttons"; import { ExpNotification } from "#enums/exp-notification"; +import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { PlayerGender } from "#enums/player-gender"; import { Species } from "#enums/species"; import { generateStarter, waitUntil } from "#test/utils/gameManagerUtils"; @@ -39,21 +45,14 @@ import PhaseInterceptor from "#test/utils/phaseInterceptor"; import TextInterceptor from "#test/utils/TextInterceptor"; import { AES, enc } from "crypto-js"; import fs from "fs"; -import { vi } from "vitest"; +import { expect, vi } from "vitest"; +import { ChallengeModeHelper } from "./helpers/challengeModeHelper"; import { ClassicModeHelper } from "./helpers/classicModeHelper"; import { DailyModeHelper } from "./helpers/dailyModeHelper"; -import { ChallengeModeHelper } from "./helpers/challengeModeHelper"; import { MoveHelper } from "./helpers/moveHelper"; import { OverridesHelper } from "./helpers/overridesHelper"; -import { SettingsHelper } from "./helpers/settingsHelper"; import { ReloadHelper } from "./helpers/reloadHelper"; -import { CheckSwitchPhase } from "#app/phases/check-switch-phase"; -import BattleMessageUiHandler from "#app/ui/battle-message-ui-handler"; -import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases"; -import { expect } from "vitest"; -import { MysteryEncounterType } from "#enums/mystery-encounter-type"; -import { isNullOrUndefined } from "#app/utils"; -import { ExpGainsSpeed } from "#app/enums/exp-gains-speed"; +import { SettingsHelper } from "./helpers/settingsHelper"; /** * Class to manage the game state and transitions between phases. diff --git a/src/test/utils/helpers/overridesHelper.ts b/src/test/utils/helpers/overridesHelper.ts index 686de58e874..b23caecfc4c 100644 --- a/src/test/utils/helpers/overridesHelper.ts +++ b/src/test/utils/helpers/overridesHelper.ts @@ -8,10 +8,10 @@ import * as GameMode from "#app/game-mode"; import { GameModes, getGameMode } from "#app/game-mode"; import { ModifierOverride } from "#app/modifier/modifier-type"; import Overrides from "#app/overrides"; +import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; +import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { vi } from "vitest"; import { GameManagerHelper } from "./gameManagerHelper"; -import { MysteryEncounterType } from "#enums/mystery-encounter-type"; -import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; /** * Helper to handle overrides in tests @@ -325,6 +325,17 @@ export class OverridesHelper extends GameManagerHelper { return this; } + /** + * Overrides the trainer AI's party + * @param species List of pokemon to generate in the party + * @returns this + */ + enemyParty(species: Species[]) { + vi.spyOn(Overrides, "TRAINER_PARTY_OVERRIDE", "get").mockReturnValue(species); + this.log("Enemy trainer party set to:", species); + return this; + } + /** * Override the encounter chance for a mystery encounter. * @param percentage the encounter chance in % diff --git a/src/test/utils/phaseInterceptor.ts b/src/test/utils/phaseInterceptor.ts index 92d4a442fad..13336b5ce48 100644 --- a/src/test/utils/phaseInterceptor.ts +++ b/src/test/utils/phaseInterceptor.ts @@ -1,5 +1,4 @@ import { Phase } from "#app/phase"; -import ErrorInterceptor from "#app/test/utils/errorInterceptor"; import { AttemptRunPhase } from "#app/phases/attempt-run-phase"; import { BattleEndPhase } from "#app/phases/battle-end-phase"; import { BerryPhase } from "#app/phases/berry-phase"; @@ -11,17 +10,28 @@ import { EncounterPhase } from "#app/phases/encounter-phase"; import { EndEvolutionPhase } from "#app/phases/end-evolution-phase"; import { EnemyCommandPhase } from "#app/phases/enemy-command-phase"; import { EvolutionPhase } from "#app/phases/evolution-phase"; +import { ExpPhase } from "#app/phases/exp-phase"; import { FaintPhase } from "#app/phases/faint-phase"; import { LearnMovePhase } from "#app/phases/learn-move-phase"; import { LevelCapPhase } from "#app/phases/level-cap-phase"; import { LoginPhase } from "#app/phases/login-phase"; import { MessagePhase } from "#app/phases/message-phase"; +import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase"; import { MoveEffectPhase } from "#app/phases/move-effect-phase"; import { MoveEndPhase } from "#app/phases/move-end-phase"; import { MovePhase } from "#app/phases/move-phase"; +import { + MysteryEncounterBattlePhase, + MysteryEncounterOptionSelectedPhase, + MysteryEncounterPhase, + MysteryEncounterRewardsPhase, + PostMysteryEncounterPhase +} from "#app/phases/mystery-encounter-phases"; import { NewBattlePhase } from "#app/phases/new-battle-phase"; import { NewBiomeEncounterPhase } from "#app/phases/new-biome-encounter-phase"; import { NextEncounterPhase } from "#app/phases/next-encounter-phase"; +import { PartyExpPhase } from "#app/phases/party-exp-phase"; +import { PartyHealPhase } from "#app/phases/party-heal-phase"; import { PostSummonPhase } from "#app/phases/post-summon-phase"; import { QuietFormChangePhase } from "#app/phases/quiet-form-change-phase"; import { SelectGenderPhase } from "#app/phases/select-gender-phase"; @@ -41,17 +51,9 @@ import { TurnInitPhase } from "#app/phases/turn-init-phase"; import { TurnStartPhase } from "#app/phases/turn-start-phase"; import { UnavailablePhase } from "#app/phases/unavailable-phase"; import { VictoryPhase } from "#app/phases/victory-phase"; -import { PartyHealPhase } from "#app/phases/party-heal-phase"; +import ErrorInterceptor from "#app/test/utils/errorInterceptor"; import UI, { Mode } from "#app/ui/ui"; -import { - MysteryEncounterBattlePhase, - MysteryEncounterOptionSelectedPhase, - MysteryEncounterPhase, - MysteryEncounterRewardsPhase, - PostMysteryEncounterPhase -} from "#app/phases/mystery-encounter-phases"; -import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase"; -import { PartyExpPhase } from "#app/phases/party-exp-phase"; +import { expect } from "vitest"; type PhaseClassType = (abstract new (...args: any) => Phase); // `typeof Phase` does not work here because of some issue with ctor signatures @@ -62,7 +64,6 @@ export interface PromptHandler { expireFn?: () => void; awaitingActionInput?: boolean; } -import { ExpPhase } from "#app/phases/exp-phase"; export default class PhaseInterceptor { public scene; @@ -227,7 +228,7 @@ export default class PhaseInterceptor { * @param skipFn - Optional skip function. * @returns A promise that resolves when the phase is run. */ - run(phaseTarget, skipFn?): Promise { + async run(phaseTarget, skipFn?): Promise { const targetName = typeof phaseTarget === "string" ? phaseTarget : phaseTarget.name; this.scene.moveAnimations = null; // Mandatory to avoid crash return new Promise(async (resolve, reject) => {