From 02c0347411508820eff69e448662f568cd623752 Mon Sep 17 00:00:00 2001 From: xsn34kzx Date: Tue, 5 Aug 2025 02:30:18 -0400 Subject: [PATCH] Add Unit Tests --- test/challenges/limited-catch.test.ts | 56 ++++++++ test/challenges/no-support.test.ts | 99 ++++++++++++++ test/challenges/permanent-faint.test.ts | 165 ++++++++++++++++++++++++ 3 files changed, 320 insertions(+) create mode 100644 test/challenges/limited-catch.test.ts create mode 100644 test/challenges/no-support.test.ts create mode 100644 test/challenges/permanent-faint.test.ts diff --git a/test/challenges/limited-catch.test.ts b/test/challenges/limited-catch.test.ts new file mode 100644 index 00000000000..87ac5351e7c --- /dev/null +++ b/test/challenges/limited-catch.test.ts @@ -0,0 +1,56 @@ +import { AbilityId } from "#enums/ability-id"; +import { Challenges } from "#enums/challenges"; +import { MoveId } from "#enums/move-id"; +import { PokeballType } from "#enums/pokeball"; +import { SpeciesId } from "#enums/species-id"; +import { GameManager } from "#test/test-utils/game-manager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +describe("Challenges - Limited Catch", () => { + 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.challengeMode.addChallenge(Challenges.LIMITED_CATCH, 1, 1); + game.override + .battleStyle("single") + .enemySpecies(SpeciesId.VOLTORB) + .enemyAbility(AbilityId.BALL_FETCH) + .enemyMoveset(MoveId.SPLASH) + .startingModifier([{ name: "MASTER_BALL", count: 1 }]) + .moveset(MoveId.RAZOR_LEAF); + }); + + it("allows Pokémon to be caught on X1 waves", async () => { + game.override.startingWave(31); + await game.challengeMode.startBattle([SpeciesId.NUZLEAF]); + + game.doThrowPokeball(PokeballType.MASTER_BALL); + await game.phaseInterceptor.to("TurnEndPhase"); + + expect(game.scene.getPlayerParty().length).toBe(2); + }); + + it("prevents Pokémon from being caught on waves that aren't X1 waves", async () => { + game.override.startingWave(53); + await game.challengeMode.startBattle([SpeciesId.NUZLEAF]); + + game.doThrowPokeball(PokeballType.MASTER_BALL); + await game.phaseInterceptor.to("TurnEndPhase"); + + expect(game.scene.getPlayerParty().length).toBe(1); + }); +}); diff --git a/test/challenges/no-support.test.ts b/test/challenges/no-support.test.ts new file mode 100644 index 00000000000..aa1254f1968 --- /dev/null +++ b/test/challenges/no-support.test.ts @@ -0,0 +1,99 @@ +import { AbilityId } from "#enums/ability-id"; +import { Challenges } from "#enums/challenges"; +import { MoveId } from "#enums/move-id"; +import { SpeciesId } from "#enums/species-id"; +import { UiMode } from "#enums/ui-mode"; +import { GameManager } from "#test/test-utils/game-manager"; +import { ModifierSelectUiHandler } from "#ui/modifier-select-ui-handler"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +describe("Challenges - No Support", () => { + 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") + .enemySpecies(SpeciesId.VOLTORB) + .enemyAbility(AbilityId.BALL_FETCH) + .enemyMoveset(MoveId.SPLASH) + .moveset(MoveId.RAZOR_LEAF); + }); + + it('disables the shop in "No Shop"', async () => { + game.override.startingWave(181); + game.challengeMode.addChallenge(Challenges.NO_SUPPORT, 2, 1); + await game.challengeMode.startBattle([SpeciesId.NUZLEAF]); + + game.move.select(MoveId.RAZOR_LEAF); + await game.doKillOpponents(); + + await game.phaseInterceptor.to("SelectModifierPhase"); + expect(game.scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); + const modifierSelectHandler = game.scene.ui.handlers.find( + h => h instanceof ModifierSelectUiHandler, + ) as ModifierSelectUiHandler; + expect(modifierSelectHandler.shopOptionsRows.length).toBe(0); + }); + + it('disables the automatic party heal in "No Heal"', async () => { + game.override.startingWave(10); + game.challengeMode.addChallenge(Challenges.NO_SUPPORT, 1, 1); + await game.challengeMode.startBattle([SpeciesId.NUZLEAF]); + + const playerPokemon = game.scene.getPlayerPokemon(); + playerPokemon!.damageAndUpdate(playerPokemon!.hp / 2); + + game.move.select(MoveId.RAZOR_LEAF); + await game.doKillOpponents(); + + await game.phaseInterceptor.to("SelectModifierPhase"); + game.doSelectModifier(); + + // Next wave + await game.phaseInterceptor.to("TurnInitPhase"); + expect(playerPokemon!.isFullHp()).toBe(false); + }); + + it('disables the automatic party heal and the shop in "Both"', async () => { + game.override.startingWave(10); + game.challengeMode.addChallenge(Challenges.NO_SUPPORT, 3, 1); + await game.challengeMode.startBattle([SpeciesId.NUZLEAF]); + + const playerPokemon = game.scene.getPlayerPokemon(); + playerPokemon!.damageAndUpdate(playerPokemon!.hp / 2); + + game.move.select(MoveId.RAZOR_LEAF); + await game.doKillOpponents(); + + await game.phaseInterceptor.to("SelectModifierPhase"); + game.doSelectModifier(); + + // Next wave + await game.phaseInterceptor.to("TurnInitPhase"); + expect(playerPokemon!.isFullHp()).toBe(false); + + game.move.select(MoveId.RAZOR_LEAF); + await game.doKillOpponents(); + + await game.phaseInterceptor.to("SelectModifierPhase"); + expect(game.scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); + const modifierSelectHandler = game.scene.ui.handlers.find( + h => h instanceof ModifierSelectUiHandler, + ) as ModifierSelectUiHandler; + expect(modifierSelectHandler.shopOptionsRows.length).toBe(0); + }); +}); diff --git a/test/challenges/permanent-faint.test.ts b/test/challenges/permanent-faint.test.ts new file mode 100644 index 00000000000..d7222097bdf --- /dev/null +++ b/test/challenges/permanent-faint.test.ts @@ -0,0 +1,165 @@ +import { Status } from "#data/status-effect"; +import { AbilityId } from "#enums/ability-id"; +import { Button } from "#enums/buttons"; +import { Challenges } from "#enums/challenges"; +import { MoveId } from "#enums/move-id"; +import { ShopCursorTarget } from "#enums/shop-cursor-target"; +import { SpeciesId } from "#enums/species-id"; +import { StatusEffect } from "#enums/status-effect"; +import { UiMode } from "#enums/ui-mode"; +import { GameManager } from "#test/test-utils/game-manager"; +import { ModifierSelectUiHandler } from "#ui/modifier-select-ui-handler"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +describe("Challenges - Permanent Faint", () => { + 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.challengeMode.addChallenge(Challenges.PERMANENT_FAINT, 1, 1); + game.override + .battleStyle("single") + .enemySpecies(SpeciesId.VOLTORB) + .enemyAbility(AbilityId.BALL_FETCH) + .enemyMoveset(MoveId.SPLASH) + .moveset(MoveId.RAZOR_LEAF); + }); + + it("disables REVIVAL_BLESSING for the player only", async () => { + game.override.enemyMoveset(MoveId.REVIVAL_BLESSING).moveset(MoveId.REVIVAL_BLESSING); + await game.challengeMode.startBattle([SpeciesId.NUZLEAF]); + + game.move.select(MoveId.REVIVAL_BLESSING); + + await game.phaseInterceptor.to("TurnEndPhase"); + + expect(game.field.getEnemyPokemon()).toHaveUsedMove(MoveId.REVIVAL_BLESSING); + expect(game.field.getPlayerPokemon()).toHaveUsedMove(MoveId.STRUGGLE); + }); + + it("prevents REVIVE items in shop and in wave rewards", async () => { + game.override.startingWave(181).startingLevel(200); + await game.challengeMode.startBattle(); + + game.move.select(MoveId.RAZOR_LEAF); + await game.doKillOpponents(); + + await game.phaseInterceptor.to("SelectModifierPhase"); + expect(game.scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); + const modifierSelectHandler = game.scene.ui.handlers.find( + h => h instanceof ModifierSelectUiHandler, + ) as ModifierSelectUiHandler; + expect( + modifierSelectHandler.options.find(reward => reward.modifierTypeOption.type.group === "revive"), + ).toBeUndefined(); + expect( + modifierSelectHandler.shopOptionsRows.find(row => + row.find(item => item.modifierTypeOption.type.group === "revive"), + ), + ).toBeUndefined(); + }); + + it("prevents the automatic party heal from reviving fainted Pokémon", async () => { + game.override.startingWave(10).startingLevel(200); + await game.challengeMode.startBattle([SpeciesId.NUZLEAF, SpeciesId.WHISMUR]); + + const faintedPokemon = game.scene.getPlayerParty()[1]; + faintedPokemon.hp = 0; + faintedPokemon.status = new Status(StatusEffect.FAINT); + expect(faintedPokemon.isFainted()).toBe(true); + + game.move.select(MoveId.RAZOR_LEAF); + await game.doKillOpponents(); + + await game.toNextWave(); + + expect(faintedPokemon.isFainted()).toBe(true); + }); + + // TODO: Couldn't figure out how to select party Pokémon + it.skip("prevents fusion with a fainted Pokémon", async () => { + game.override.itemRewards([{ name: "DNA_SPLICERS" }]); + await game.challengeMode.startBattle([SpeciesId.NUZLEAF, SpeciesId.WHISMUR]); + + const faintedPokemon = game.scene.getPlayerParty()[1]; + faintedPokemon.hp = 0; + faintedPokemon.status = new Status(StatusEffect.FAINT); + expect(faintedPokemon.isFainted()).toBe(true); + + game.move.select(MoveId.RAZOR_LEAF); + await game.doKillOpponents(); + + await game.phaseInterceptor.to("SelectModifierPhase"); + game.onNextPrompt( + "SelectModifierPhase", + UiMode.MODIFIER_SELECT, + () => { + const handler = game.scene.ui.getHandler() as ModifierSelectUiHandler; + // Traverse to and select first modifier + handler.setCursor(0); + handler.setRowCursor(ShopCursorTarget.REWARDS); + handler.processInput(Button.ACTION); + + // Go to fainted Pokémon and try to select it + handler.processInput(Button.RIGHT); + handler.processInput(Button.ACTION); + handler.processInput(Button.ACTION); + handler.processInput(Button.ACTION); + + expect(game.scene.getPlayerParty().length).toBe(2); + }, + () => game.isCurrentPhase("CommandPhase") || game.isCurrentPhase("NewBattlePhase"), + true, + ); + }); + + // TODO: Couldn't figure out how to select party Pokémon + it.skip("prevents fainted Pokémon from being revived", async () => { + game.override.itemRewards([{ name: "MAX_REVIVE" }]); + await game.challengeMode.startBattle([SpeciesId.NUZLEAF, SpeciesId.WHISMUR]); + + const faintedPokemon = game.scene.getPlayerParty()[1]; + faintedPokemon.hp = 0; + faintedPokemon.status = new Status(StatusEffect.FAINT); + expect(faintedPokemon.isFainted()).toBe(true); + + game.move.select(MoveId.RAZOR_LEAF); + await game.doKillOpponents(); + + await game.phaseInterceptor.to("SelectModifierPhase"); + game.onNextPrompt( + "SelectModifierPhase", + UiMode.MODIFIER_SELECT, + () => { + const handler = game.scene.ui.getHandler() as ModifierSelectUiHandler; + // Traverse to and select first modifier + handler.setCursor(0); + handler.setRowCursor(ShopCursorTarget.REWARDS); + handler.processInput(Button.ACTION); + + // Go to fainted Pokémon and try to select it + handler.processInput(Button.RIGHT); + handler.processInput(Button.ACTION); + handler.processInput(Button.ACTION); + handler.processInput(Button.ACTION); + + expect(faintedPokemon.isFainted()).toBe(true); + }, + () => game.isCurrentPhase("CommandPhase") || game.isCurrentPhase("NewBattlePhase"), + true, + ); + }); +});