[Improvement] Refactored Test Phase Management with Error Handling (#1962)

* better phase management in tests

* cleanup runFrom/to

* first step of async error handling

* second step of async error handling, halt await, still an issue with select-starter test

* added back whenAboutToRun

* added back full run suite for starter-select.test.ts
This commit is contained in:
Greenlamp2 2024-06-08 21:54:20 +02:00 committed by GitHub
parent 0af0ad5b49
commit c301a54039
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 529 additions and 422 deletions

View File

@ -44,8 +44,8 @@ describe("Abilities - Intrepid Sword", () => {
expect(game.scene.getParty()[0].summonData).not.toBeUndefined();
let battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
expect(battleStatsPokemon[BattleStat.ATK]).toBe(0);
await game.phaseInterceptor.mustRun(ShowAbilityPhase).catch((error) => expect(error).toBe(ShowAbilityPhase));
await game.phaseInterceptor.mustRun(StatChangePhase).catch((error) => expect(error).toBe(StatChangePhase));
await game.phaseInterceptor.run(ShowAbilityPhase);
await game.phaseInterceptor.run(StatChangePhase);
battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
expect(battleStatsPokemon[BattleStat.ATK]).toBe(1);
}, 20000);
@ -57,8 +57,8 @@ describe("Abilities - Intrepid Sword", () => {
let battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
expect(battleStatsOpponent[BattleStat.ATK]).toBe(0);
await game.phaseInterceptor.runFrom(PostSummonPhase).to(ToggleDoublePositionPhase);
await game.phaseInterceptor.mustRun(StatChangePhase).catch((error) => expect(error).toBe(StatChangePhase));
await game.phaseInterceptor.whenAboutToRun(MessagePhase);
await game.phaseInterceptor.run(StatChangePhase);
await game.phaseInterceptor.run(MessagePhase);
battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
expect(battleStatsOpponent[BattleStat.ATK]).toBe(1);
}, 20000);

View File

@ -5,7 +5,7 @@ import * as overrides from "#app/overrides";
import {Abilities} from "#app/data/enums/abilities";
import {Species} from "#app/data/enums/species";
import {
CommandPhase, EnemyCommandPhase,
CommandPhase, EnemyCommandPhase, SelectTargetPhase,
TurnStartPhase
} from "#app/phases";
import {Mode} from "#app/ui/ui";
@ -55,7 +55,6 @@ describe("Battle order", () => {
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false);
});
await game.phaseInterceptor.run(EnemyCommandPhase);
await game.phaseInterceptor.whenAboutToRun(TurnStartPhase);
const phase = game.scene.getCurrentPhase() as TurnStartPhase;
const order = phase.getOrder();
expect(order[0]).toBe(2);
@ -77,7 +76,6 @@ describe("Battle order", () => {
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false);
});
await game.phaseInterceptor.run(EnemyCommandPhase);
await game.phaseInterceptor.whenAboutToRun(TurnStartPhase);
const phase = game.scene.getCurrentPhase() as TurnStartPhase;
const order = phase.getOrder();
expect(order[0]).toBe(0);
@ -118,9 +116,7 @@ describe("Battle order", () => {
const handler = game.scene.ui.getHandler() as TargetSelectUiHandler;
handler.processInput(Button.ACTION);
});
await game.phaseInterceptor.runFrom(CommandPhase).to(EnemyCommandPhase);
await game.phaseInterceptor.run(EnemyCommandPhase);
await game.phaseInterceptor.whenAboutToRun(TurnStartPhase);
await game.phaseInterceptor.runFrom(SelectTargetPhase).to(TurnStartPhase, false);
const phase = game.scene.getCurrentPhase() as TurnStartPhase;
const order = phase.getOrder();
expect(order.indexOf(0)).toBeGreaterThan(order.indexOf(2));
@ -163,9 +159,7 @@ describe("Battle order", () => {
const handler = game.scene.ui.getHandler() as TargetSelectUiHandler;
handler.processInput(Button.ACTION);
});
await game.phaseInterceptor.runFrom(CommandPhase).to(EnemyCommandPhase);
await game.phaseInterceptor.run(EnemyCommandPhase);
await game.phaseInterceptor.whenAboutToRun(TurnStartPhase);
await game.phaseInterceptor.runFrom(SelectTargetPhase).to(TurnStartPhase, false);
const phase = game.scene.getCurrentPhase() as TurnStartPhase;
const order = phase.getOrder();
expect(order.indexOf(3)).toBeLessThan(order.indexOf(0));
@ -207,9 +201,7 @@ describe("Battle order", () => {
const handler = game.scene.ui.getHandler() as TargetSelectUiHandler;
handler.processInput(Button.ACTION);
});
await game.phaseInterceptor.runFrom(CommandPhase).to(EnemyCommandPhase);
await game.phaseInterceptor.run(EnemyCommandPhase);
await game.phaseInterceptor.whenAboutToRun(TurnStartPhase);
await game.phaseInterceptor.runFrom(SelectTargetPhase).to(TurnStartPhase, false);
const phase = game.scene.getCurrentPhase() as TurnStartPhase;
const order = phase.getOrder();
expect(order.indexOf(1)).toBeLessThan(order.indexOf(0));

View File

@ -1,34 +1,21 @@
import {afterEach, beforeAll, beforeEach, describe, expect, it, vi} from "vitest";
import {generateStarter, getMovePosition, waitUntil,} from "#app/test/utils/gameManagerUtils";
import {generateStarter, getMovePosition,} from "#app/test/utils/gameManagerUtils";
import {Mode} from "#app/ui/ui";
import {GameModes} from "#app/game-mode";
import {Species} from "#app/data/enums/species";
import * as overrides from "../../overrides";
import {Command} from "#app/ui/command-ui-handler";
import {
BattleEndPhase,
BerryPhase,
CommandPhase,
DamagePhase,
EggLapsePhase,
EncounterPhase,
EnemyCommandPhase,
FaintPhase,
LoginPhase,
MessagePhase,
MoveEffectPhase,
MoveEndPhase,
MovePhase,
PostSummonPhase,
SelectGenderPhase,
SelectModifierPhase,
SelectStarterPhase,
StatChangePhase,
SummonPhase,
TitlePhase,
TurnEndPhase,
TurnInitPhase,
TurnStartPhase,
VictoryPhase,
} from "#app/phases";
import {Moves} from "#app/data/enums/moves";
import GameManager from "#app/test/utils/gameManager";
@ -36,6 +23,7 @@ import Phaser from "phaser";
import {allSpecies} from "#app/data/pokemon-species";
import {PlayerGender} from "#app/data/enums/player-gender";
import { getGameMode } from "#app/game-mode.js";
import {Abilities} from "#app/data/enums/abilities";
describe("Test Battle Phase", () => {
let phaserGame: Phaser.Game;
@ -55,22 +43,6 @@ describe("Test Battle Phase", () => {
game = new GameManager(phaserGame);
});
it("test phase interceptor with remove", async() => {
await game.phaseInterceptor.run(LoginPhase);
await game.phaseInterceptor.run(LoginPhase, () => {
return game.phaseInterceptor.log.includes("LoginPhase");
});
game.scene.gameData.gender = PlayerGender.MALE;
await game.phaseInterceptor.remove(SelectGenderPhase, () => game.isCurrentPhase(TitlePhase));
await game.phaseInterceptor.run(TitlePhase);
await waitUntil(() => game.scene.ui?.getMode() === Mode.TITLE);
expect(game.scene.ui?.getMode()).toBe(Mode.TITLE);
}, 100000);
it("test phase interceptor with prompt", async() => {
await game.phaseInterceptor.run(LoginPhase);
@ -87,7 +59,7 @@ describe("Test Battle Phase", () => {
expect(game.scene.ui?.getMode()).toBe(Mode.TITLE);
expect(game.scene.gameData.gender).toBe(PlayerGender.MALE);
}, 100000);
}, 20000);
it("test phase interceptor with prompt with preparation for a future prompt", async() => {
await game.phaseInterceptor.run(LoginPhase);
@ -109,13 +81,13 @@ describe("Test Battle Phase", () => {
expect(game.scene.ui?.getMode()).toBe(Mode.TITLE);
expect(game.scene.gameData.gender).toBe(PlayerGender.MALE);
}, 100000);
}, 20000);
it("newGame one-liner", async() => {
await game.startBattle();
expect(game.scene.ui?.getMode()).toBe(Mode.COMMAND);
expect(game.scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name);
}, 100000);
}, 20000);
it("do attack wave 3 - single battle - regular - OHKO", async() => {
vi.spyOn(overrides, "STARTER_SPECIES_OVERRIDE", "get").mockReturnValue(Species.MEWTWO);
@ -123,6 +95,8 @@ describe("Test Battle Phase", () => {
vi.spyOn(overrides, "STARTING_LEVEL_OVERRIDE", "get").mockReturnValue(2000);
vi.spyOn(overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(3);
vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE]);
vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.HYDRATION);
vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE]);
vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
await game.startBattle();
game.onNextPrompt("CommandPhase", Mode.COMMAND, () => {
@ -132,28 +106,10 @@ describe("Test Battle Phase", () => {
const movePosition = getMovePosition(game.scene, 0, Moves.TACKLE);
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false);
});
await game.phaseInterceptor.run(EnemyCommandPhase);
await game.phaseInterceptor.run(TurnStartPhase);
await game.phaseInterceptor.run(MovePhase);
await game.phaseInterceptor.run(MessagePhase);
await game.phaseInterceptor.run(MoveEffectPhase);
await game.phaseInterceptor.run(DamagePhase);
await game.phaseInterceptor.run(MessagePhase, () => game.isCurrentPhase(FaintPhase));
await game.phaseInterceptor.run(FaintPhase);
await game.phaseInterceptor.run(MessagePhase);
await game.phaseInterceptor.run(VictoryPhase);
await game.phaseInterceptor.run(MoveEndPhase);
await game.phaseInterceptor.run(MovePhase);
await game.phaseInterceptor.run(BerryPhase);
await game.phaseInterceptor.run(TurnEndPhase);
await game.phaseInterceptor.run(BattleEndPhase);
await game.phaseInterceptor.run(EggLapsePhase);
await game.phaseInterceptor.run(SelectModifierPhase);
await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(SelectModifierPhase);
expect(game.scene.ui?.getMode()).toBe(Mode.MODIFIER_SELECT);
expect(game.scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name);
}, 100000);
}, 20000);
it("do attack wave 3 - single battle - regular - NO OHKO with opponent using non damage attack", async() => {
vi.spyOn(overrides, "STARTER_SPECIES_OVERRIDE", "get").mockReturnValue(Species.MEWTWO);
@ -161,7 +117,8 @@ describe("Test Battle Phase", () => {
vi.spyOn(overrides, "STARTING_LEVEL_OVERRIDE", "get").mockReturnValue(5);
vi.spyOn(overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(3);
vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE]);
vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TAIL_WHIP]);
vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.HYDRATION);
vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TAIL_WHIP, Moves.TAIL_WHIP, Moves.TAIL_WHIP, Moves.TAIL_WHIP]);
vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
await game.startBattle();
game.onNextPrompt("CommandPhase", Mode.COMMAND, () => {
@ -171,35 +128,8 @@ describe("Test Battle Phase", () => {
const movePosition = getMovePosition(game.scene, 0, Moves.TACKLE);
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false);
});
await game.phaseInterceptor.run(EnemyCommandPhase);
await game.phaseInterceptor.run(TurnStartPhase);
await game.phaseInterceptor.run(MovePhase);
await game.phaseInterceptor.run(MessagePhase);
await game.phaseInterceptor.run(MoveEffectPhase);
await game.phaseInterceptor.run(DamagePhase);
await game.phaseInterceptor.run(MessagePhase, () => game.isCurrentPhase(MoveEndPhase));
await game.phaseInterceptor.run(MoveEndPhase);
await game.phaseInterceptor.run(MovePhase);
await game.phaseInterceptor.run(MessagePhase, () => game.isCurrentPhase(MoveEffectPhase));
await game.phaseInterceptor.run(MoveEffectPhase);
game.scene.moveAnimations = null; // Mandatory to avoid the crash
await game.phaseInterceptor.run(StatChangePhase, () => game.isCurrentPhase(MessagePhase) || game.isCurrentPhase(MoveEndPhase) || game.isCurrentPhase(DamagePhase));
await game.phaseInterceptor.run(DamagePhase, () => game.isCurrentPhase(MessagePhase) || game.isCurrentPhase(MoveEndPhase));
await game.phaseInterceptor.run(MessagePhase, () => game.isCurrentPhase(MoveEndPhase));
await game.phaseInterceptor.run(MoveEndPhase);
await game.phaseInterceptor.run(BerryPhase);
await game.phaseInterceptor.run(MessagePhase, () => game.isCurrentPhase(TurnEndPhase));
await game.phaseInterceptor.run(TurnEndPhase);
await game.phaseInterceptor.run(TurnInitPhase);
await game.phaseInterceptor.run(CommandPhase);
await waitUntil(() => game.scene.ui?.getMode() === Mode.COMMAND);
expect(game.scene.ui?.getMode()).toBe(Mode.COMMAND);
expect(game.scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name);
}, 100000);
await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(TurnInitPhase);
}, 20000);
it("load 100% data file", async() => {
await game.importData("src/test/utils/saves/everything.prsv");
@ -208,7 +138,7 @@ describe("Test Battle Phase", () => {
return species.caughtAttr !== 0n;
}).length;
expect(caughtCount).toBe(Object.keys(allSpecies).length);
}, 50000);
}, 20000);
it("start battle with selected team", async() => {
await game.startBattle([
@ -219,26 +149,7 @@ describe("Test Battle Phase", () => {
expect(game.scene.getParty()[0].species.speciesId).toBe(Species.CHARIZARD);
expect(game.scene.getParty()[1].species.speciesId).toBe(Species.CHANSEY);
expect(game.scene.getParty()[2].species.speciesId).toBe(Species.MEW);
}, 50000);
it("assert next phase", async() => {
await game.phaseInterceptor.run(LoginPhase);
game.onNextPrompt("SelectGenderPhase", Mode.OPTION_SELECT, () => {
game.scene.gameData.gender = PlayerGender.MALE;
game.endPhase();
}, () => game.isCurrentPhase(TitlePhase));
await game.phaseInterceptor.mustRun(SelectGenderPhase).catch((error) => expect(error).toBe(SelectGenderPhase));
await game.phaseInterceptor.mustRun(TitlePhase).catch((error) => expect(error).toBe(TitlePhase));
game.onNextPrompt("TitlePhase", Mode.TITLE, () => {
game.scene.gameMode = getGameMode(GameModes.CLASSIC);
const starters = generateStarter(game.scene);
const selectStarterPhase = new SelectStarterPhase(game.scene);
game.scene.pushPhase(new EncounterPhase(game.scene, false));
selectStarterPhase.initBattle(starters);
});
await game.phaseInterceptor.mustRun(EncounterPhase).catch((error) => expect(error).toBe(EncounterPhase));
await game.phaseInterceptor.mustRun(PostSummonPhase).catch((error) => expect(error).toBe(PostSummonPhase));
}, 50000);
}, 20000);
it("test remove random battle seed int", async() => {
for (let i=0; i<10; i++) {
@ -246,5 +157,107 @@ describe("Test Battle Phase", () => {
expect(rand).toBe(14);
}
});
it("wrong phase", async() => {
await game.phaseInterceptor.run(LoginPhase);
await game.phaseInterceptor.run(LoginPhase).catch((e) => {
expect(e).toBe("Wrong phase: this is SelectGenderPhase and not LoginPhase");
});
}, 20000);
it("wrong phase but skip", async() => {
await game.phaseInterceptor.run(LoginPhase);
await game.phaseInterceptor.run(LoginPhase, () => game.isCurrentPhase(SelectGenderPhase));
}, 20000);
it("good run", async() => {
await game.phaseInterceptor.run(LoginPhase);
game.onNextPrompt("SelectGenderPhase", Mode.OPTION_SELECT, () => {
game.scene.gameData.gender = PlayerGender.MALE;
game.endPhase();
}, () => game.isCurrentPhase(TitlePhase));
await game.phaseInterceptor.run(SelectGenderPhase, () => game.isCurrentPhase(TitlePhase));
await game.phaseInterceptor.run(TitlePhase);
}, 20000);
it("good run from select gender to title", async() => {
await game.phaseInterceptor.run(LoginPhase);
game.onNextPrompt("SelectGenderPhase", Mode.OPTION_SELECT, () => {
game.scene.gameData.gender = PlayerGender.MALE;
game.endPhase();
}, () => game.isCurrentPhase(TitlePhase));
await game.phaseInterceptor.runFrom(SelectGenderPhase).to(TitlePhase);
}, 20000);
it("good run to SummonPhase phase", async() => {
await game.phaseInterceptor.run(LoginPhase);
game.onNextPrompt("SelectGenderPhase", Mode.OPTION_SELECT, () => {
game.scene.gameData.gender = PlayerGender.MALE;
game.endPhase();
}, () => game.isCurrentPhase(TitlePhase));
game.onNextPrompt("TitlePhase", Mode.TITLE, () => {
game.scene.gameMode = getGameMode(GameModes.CLASSIC);
const starters = generateStarter(game.scene);
const selectStarterPhase = new SelectStarterPhase(game.scene);
game.scene.pushPhase(new EncounterPhase(game.scene, false));
selectStarterPhase.initBattle(starters);
});
await game.phaseInterceptor.runFrom(SelectGenderPhase).to(SummonPhase);
}, 20000);
it("2vs1", async() => {
vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.MIGHTYENA);
vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.HYDRATION);
vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.HYDRATION);
await game.startBattle([
Species.BLASTOISE,
Species.CHARIZARD,
]);
expect(game.scene.ui?.getMode()).toBe(Mode.COMMAND);
expect(game.scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name);
}, 20000);
it("1vs1", async() => {
vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.MIGHTYENA);
vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.HYDRATION);
vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.HYDRATION);
await game.startBattle([
Species.BLASTOISE,
]);
expect(game.scene.ui?.getMode()).toBe(Mode.COMMAND);
expect(game.scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name);
}, 20000);
it("2vs2", async() => {
vi.spyOn(overrides, "DOUBLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.MIGHTYENA);
vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.HYDRATION);
vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.HYDRATION);
vi.spyOn(overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(3);
await game.startBattle([
Species.BLASTOISE,
Species.CHARIZARD,
]);
expect(game.scene.ui?.getMode()).toBe(Mode.COMMAND);
expect(game.scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name);
}, 20000);
it("4vs2", async() => {
vi.spyOn(overrides, "DOUBLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.MIGHTYENA);
vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.HYDRATION);
vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.HYDRATION);
vi.spyOn(overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(3);
await game.startBattle([
Species.BLASTOISE,
Species.CHARIZARD,
Species.DARKRAI,
Species.GABITE,
]);
expect(game.scene.ui?.getMode()).toBe(Mode.COMMAND);
expect(game.scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name);
}, 20000);
});

View File

@ -0,0 +1,39 @@
import {afterEach, beforeAll, beforeEach, describe, it, vi} from "vitest";
import GameManager from "#app/test/utils/gameManager";
import Phaser from "phaser";
import * as overrides from "#app/overrides";
import {Species} from "#app/data/enums/species";
import {Moves} from "#app/data/enums/moves";
import {Abilities} from "#app/data/enums/abilities";
describe("Test Battle Phase", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
});
it("should start phase", async() => {
vi.spyOn(overrides, "STARTER_SPECIES_OVERRIDE", "get").mockReturnValue(Species.MEWTWO);
vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.RATTATA);
vi.spyOn(overrides, "STARTING_LEVEL_OVERRIDE", "get").mockReturnValue(2000);
vi.spyOn(overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(3);
vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE]);
vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.HYDRATION);
vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE]);
vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
await game.startBattle();
}, 100000);
});

View File

@ -28,7 +28,8 @@ describe("Phases", () => {
describe("LoginPhase", () => {
it("should start the login phase", async () => {
const loginPhase = new LoginPhase(scene);
loginPhase.start();
scene.pushPhase(loginPhase);
await game.phaseInterceptor.run(LoginPhase);
expect(scene.ui.getMode()).to.equal(Mode.MESSAGE);
});
});
@ -36,16 +37,18 @@ describe("Phases", () => {
describe("TitlePhase", () => {
it("should start the title phase", async () => {
const titlePhase = new TitlePhase(scene);
titlePhase.start();
expect(scene.ui.getMode()).to.equal(Mode.MESSAGE);
scene.pushPhase(titlePhase);
await game.phaseInterceptor.run(TitlePhase);
expect(scene.ui.getMode()).to.equal(Mode.TITLE);
});
});
describe("UnavailablePhase", () => {
it("should start the unavailable phase", async () => {
const unavailablePhase = new UnavailablePhase(scene);
unavailablePhase.start();
scene.pushPhase(unavailablePhase);
await game.phaseInterceptor.run(UnavailablePhase);
expect(scene.ui.getMode()).to.equal(Mode.UNAVAILABLE);
});
}, 20000);
});
});

View File

@ -15,9 +15,9 @@ import OptionSelectUiHandler from "#app/ui/settings/option-select-ui-handler";
import SaveSlotSelectUiHandler from "#app/ui/save-slot-select-ui-handler";
import {OptionSelectItem} from "#app/ui/abstact-option-select-ui-handler";
import {Gender} from "#app/data/gender";
import {allSpecies} from "#app/data/pokemon-species";
import {Nature} from "#app/data/nature";
import {Abilities} from "#app/data/enums/abilities";
import {allSpecies} from "#app/data/pokemon-species";
describe("UI - Starter select", () => {
@ -51,12 +51,13 @@ describe("UI - Starter select", () => {
currentPhase.gameMode = GameModes.CLASSIC;
currentPhase.end();
});
await game.phaseInterceptor.mustRun(SelectStarterPhase).catch((error) => expect(error).toBe(SelectStarterPhase));
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.RIGHT);
handler.processInput(Button.ACTION);
game.phaseInterceptor.unlock();
});
await game.phaseInterceptor.run(SelectStarterPhase);
let options: OptionSelectItem[];
let optionSelectUiHandler: OptionSelectUiHandler;
await new Promise<void>((resolve) => {
@ -73,21 +74,20 @@ describe("UI - Starter select", () => {
expect(options.some(option => option.label === "Cancel")).toBe(true);
optionSelectUiHandler.processInput(Button.ACTION);
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.SUBMIT);
});
game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.ACTION);
});
game.onNextPrompt("SelectStarterPhase", Mode.SAVE_SLOT, () => {
const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler;
saveSlotSelectUiHandler.processInput(Button.ACTION);
});
game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.ACTION);
await new Promise<void>((resolve) => {
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.SUBMIT);
});
game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.ACTION);
});
game.onNextPrompt("SelectStarterPhase", Mode.SAVE_SLOT, () => {
const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler;
saveSlotSelectUiHandler.processInput(Button.ACTION);
resolve();
});
});
await game.phaseInterceptor.whenAboutToRun(EncounterPhase);
@ -99,19 +99,25 @@ describe("UI - Starter select", () => {
it("Bulbasaur - shiny - variant 2 female hardy overgrow", async() => {
await game.importData("src/test/utils/saves/everything.prsv");
const caughtCount = Object.keys(game.scene.gameData.dexData).filter((key) => {
const species = game.scene.gameData.dexData[key];
return species.caughtAttr !== 0n;
}).length;
expect(caughtCount).toBe(Object.keys(allSpecies).length);
await game.runToTitle();
game.onNextPrompt("TitlePhase", Mode.TITLE, () => {
const currentPhase = game.scene.getCurrentPhase() as TitlePhase;
currentPhase.gameMode = GameModes.CLASSIC;
currentPhase.end();
});
await game.phaseInterceptor.mustRun(SelectStarterPhase).catch((error) => expect(error).toBe(SelectStarterPhase));
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.RIGHT);
handler.processInput(Button.CYCLE_GENDER);
handler.processInput(Button.ACTION);
game.phaseInterceptor.unlock();
});
await game.phaseInterceptor.run(SelectStarterPhase);
let options: OptionSelectItem[];
let optionSelectUiHandler: OptionSelectUiHandler;
await new Promise<void>((resolve) => {
@ -128,21 +134,20 @@ describe("UI - Starter select", () => {
expect(options.some(option => option.label === "Cancel")).toBe(true);
optionSelectUiHandler.processInput(Button.ACTION);
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.SUBMIT);
});
game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.ACTION);
});
game.onNextPrompt("SelectStarterPhase", Mode.SAVE_SLOT, () => {
const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler;
saveSlotSelectUiHandler.processInput(Button.ACTION);
});
game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.ACTION);
await new Promise<void>((resolve) => {
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.SUBMIT);
});
game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.ACTION);
});
game.onNextPrompt("SelectStarterPhase", Mode.SAVE_SLOT, () => {
const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler;
saveSlotSelectUiHandler.processInput(Button.ACTION);
resolve();
});
});
await game.phaseInterceptor.whenAboutToRun(EncounterPhase);
@ -153,15 +158,19 @@ describe("UI - Starter select", () => {
expect(game.scene.getParty()[0].getAbility().id).toBe(Abilities.OVERGROW);
}, 20000);
it("Bulbasaur - shiny - variant 2 female lonely cholorophyl", async() => {
it("Bulbasaur - shiny - variant 2 female lonely chlorophyl", async() => {
await game.importData("src/test/utils/saves/everything.prsv");
const caughtCount = Object.keys(game.scene.gameData.dexData).filter((key) => {
const species = game.scene.gameData.dexData[key];
return species.caughtAttr !== 0n;
}).length;
expect(caughtCount).toBe(Object.keys(allSpecies).length);
await game.runToTitle();
game.onNextPrompt("TitlePhase", Mode.TITLE, () => {
const currentPhase = game.scene.getCurrentPhase() as TitlePhase;
currentPhase.gameMode = GameModes.CLASSIC;
currentPhase.end();
});
await game.phaseInterceptor.mustRun(SelectStarterPhase).catch((error) => expect(error).toBe(SelectStarterPhase));
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.RIGHT);
@ -169,7 +178,9 @@ describe("UI - Starter select", () => {
handler.processInput(Button.CYCLE_NATURE);
handler.processInput(Button.CYCLE_ABILITY);
handler.processInput(Button.ACTION);
game.phaseInterceptor.unlock();
});
await game.phaseInterceptor.run(SelectStarterPhase);
let options: OptionSelectItem[];
let optionSelectUiHandler: OptionSelectUiHandler;
await new Promise<void>((resolve) => {
@ -186,21 +197,20 @@ describe("UI - Starter select", () => {
expect(options.some(option => option.label === "Cancel")).toBe(true);
optionSelectUiHandler.processInput(Button.ACTION);
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.SUBMIT);
});
game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.ACTION);
});
game.onNextPrompt("SelectStarterPhase", Mode.SAVE_SLOT, () => {
const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler;
saveSlotSelectUiHandler.processInput(Button.ACTION);
});
game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.ACTION);
await new Promise<void>((resolve) => {
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.SUBMIT);
});
game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.ACTION);
});
game.onNextPrompt("SelectStarterPhase", Mode.SAVE_SLOT, () => {
const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler;
saveSlotSelectUiHandler.processInput(Button.ACTION);
resolve();
});
});
await game.phaseInterceptor.whenAboutToRun(EncounterPhase);
@ -211,21 +221,27 @@ describe("UI - Starter select", () => {
expect(game.scene.getParty()[0].getAbility().id).toBe(Abilities.CHLOROPHYLL);
}, 20000);
it("Bulbasaur - shiny - variant 2 female", async() => {
it("Bulbasaur - shiny - variant 2 female lonely chlorophyl", async() => {
await game.importData("src/test/utils/saves/everything.prsv");
const caughtCount = Object.keys(game.scene.gameData.dexData).filter((key) => {
const species = game.scene.gameData.dexData[key];
return species.caughtAttr !== 0n;
}).length;
expect(caughtCount).toBe(Object.keys(allSpecies).length);
await game.runToTitle();
game.onNextPrompt("TitlePhase", Mode.TITLE, () => {
const currentPhase = game.scene.getCurrentPhase() as TitlePhase;
currentPhase.gameMode = GameModes.CLASSIC;
currentPhase.end();
});
await game.phaseInterceptor.mustRun(SelectStarterPhase).catch((error) => expect(error).toBe(SelectStarterPhase));
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.RIGHT);
handler.processInput(Button.CYCLE_GENDER);
handler.processInput(Button.ACTION);
game.phaseInterceptor.unlock();
});
await game.phaseInterceptor.run(SelectStarterPhase);
let options: OptionSelectItem[];
let optionSelectUiHandler: OptionSelectUiHandler;
await new Promise<void>((resolve) => {
@ -242,21 +258,20 @@ describe("UI - Starter select", () => {
expect(options.some(option => option.label === "Cancel")).toBe(true);
optionSelectUiHandler.processInput(Button.ACTION);
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.SUBMIT);
});
game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.ACTION);
});
game.onNextPrompt("SelectStarterPhase", Mode.SAVE_SLOT, () => {
const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler;
saveSlotSelectUiHandler.processInput(Button.ACTION);
});
game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.ACTION);
await new Promise<void>((resolve) => {
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.SUBMIT);
});
game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.ACTION);
});
game.onNextPrompt("SelectStarterPhase", Mode.SAVE_SLOT, () => {
const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler;
saveSlotSelectUiHandler.processInput(Button.ACTION);
resolve();
});
});
await game.phaseInterceptor.whenAboutToRun(EncounterPhase);
@ -268,19 +283,25 @@ describe("UI - Starter select", () => {
it("Bulbasaur - not shiny", async() => {
await game.importData("src/test/utils/saves/everything.prsv");
const caughtCount = Object.keys(game.scene.gameData.dexData).filter((key) => {
const species = game.scene.gameData.dexData[key];
return species.caughtAttr !== 0n;
}).length;
expect(caughtCount).toBe(Object.keys(allSpecies).length);
await game.runToTitle();
game.onNextPrompt("TitlePhase", Mode.TITLE, () => {
const currentPhase = game.scene.getCurrentPhase() as TitlePhase;
currentPhase.gameMode = GameModes.CLASSIC;
currentPhase.end();
});
await game.phaseInterceptor.mustRun(SelectStarterPhase).catch((error) => expect(error).toBe(SelectStarterPhase));
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.RIGHT);
handler.processInput(Button.CYCLE_SHINY);
handler.processInput(Button.ACTION);
game.phaseInterceptor.unlock();
});
await game.phaseInterceptor.run(SelectStarterPhase);
let options: OptionSelectItem[];
let optionSelectUiHandler: OptionSelectUiHandler;
await new Promise<void>((resolve) => {
@ -297,21 +318,20 @@ describe("UI - Starter select", () => {
expect(options.some(option => option.label === "Cancel")).toBe(true);
optionSelectUiHandler.processInput(Button.ACTION);
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.SUBMIT);
});
game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.ACTION);
});
game.onNextPrompt("SelectStarterPhase", Mode.SAVE_SLOT, () => {
const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler;
saveSlotSelectUiHandler.processInput(Button.ACTION);
});
game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.ACTION);
await new Promise<void>((resolve) => {
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.SUBMIT);
});
game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.ACTION);
});
game.onNextPrompt("SelectStarterPhase", Mode.SAVE_SLOT, () => {
const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler;
saveSlotSelectUiHandler.processInput(Button.ACTION);
resolve();
});
});
await game.phaseInterceptor.whenAboutToRun(EncounterPhase);
@ -320,76 +340,28 @@ describe("UI - Starter select", () => {
expect(game.scene.getParty()[0].variant).toBe(0);
}, 20000);
it("Bulbasaur - shiny - variant 0", async() => {
await game.importData("src/test/utils/saves/everything.prsv");
await game.runToTitle();
game.onNextPrompt("TitlePhase", Mode.TITLE, () => {
const currentPhase = game.scene.getCurrentPhase() as TitlePhase;
currentPhase.gameMode = GameModes.CLASSIC;
currentPhase.end();
});
await game.phaseInterceptor.mustRun(SelectStarterPhase).catch((error) => expect(error).toBe(SelectStarterPhase));
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.RIGHT);
handler.processInput(Button.V);
handler.processInput(Button.ACTION);
});
let options: OptionSelectItem[];
let optionSelectUiHandler: OptionSelectUiHandler;
await new Promise<void>((resolve) => {
game.onNextPrompt("SelectStarterPhase", Mode.OPTION_SELECT, () => {
optionSelectUiHandler = game.scene.ui.getHandler() as OptionSelectUiHandler;
options = optionSelectUiHandler.getOptionsWithScroll();
resolve();
});
});
expect(options.some(option => option.label === "Add to Party")).toBe(true);
expect(options.some(option => option.label === "Toggle IVs")).toBe(true);
expect(options.some(option => option.label === "Manage Moves")).toBe(true);
expect(options.some(option => option.label === "Use Candies")).toBe(true);
expect(options.some(option => option.label === "Cancel")).toBe(true);
optionSelectUiHandler.processInput(Button.ACTION);
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.SUBMIT);
});
game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.ACTION);
});
game.onNextPrompt("SelectStarterPhase", Mode.SAVE_SLOT, () => {
const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler;
saveSlotSelectUiHandler.processInput(Button.ACTION);
});
game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.ACTION);
});
await game.phaseInterceptor.whenAboutToRun(EncounterPhase);
expect(game.scene.getParty()[0].species.speciesId).toBe(Species.BULBASAUR);
expect(game.scene.getParty()[0].shiny).toBe(true);
expect(game.scene.getParty()[0].variant).toBe(0);
}, 20000);
it("Bulbasaur - shiny - variant 1", async() => {
await game.importData("src/test/utils/saves/everything.prsv");
const caughtCount = Object.keys(game.scene.gameData.dexData).filter((key) => {
const species = game.scene.gameData.dexData[key];
return species.caughtAttr !== 0n;
}).length;
expect(caughtCount).toBe(Object.keys(allSpecies).length);
await game.runToTitle();
game.onNextPrompt("TitlePhase", Mode.TITLE, () => {
const currentPhase = game.scene.getCurrentPhase() as TitlePhase;
currentPhase.gameMode = GameModes.CLASSIC;
currentPhase.end();
});
await game.phaseInterceptor.mustRun(SelectStarterPhase).catch((error) => expect(error).toBe(SelectStarterPhase));
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.RIGHT);
handler.processInput(Button.V);
handler.processInput(Button.V);
handler.processInput(Button.ACTION);
game.phaseInterceptor.unlock();
});
await game.phaseInterceptor.run(SelectStarterPhase);
let options: OptionSelectItem[];
let optionSelectUiHandler: OptionSelectUiHandler;
await new Promise<void>((resolve) => {
@ -406,21 +378,20 @@ describe("UI - Starter select", () => {
expect(options.some(option => option.label === "Cancel")).toBe(true);
optionSelectUiHandler.processInput(Button.ACTION);
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.SUBMIT);
});
game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.ACTION);
});
game.onNextPrompt("SelectStarterPhase", Mode.SAVE_SLOT, () => {
const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler;
saveSlotSelectUiHandler.processInput(Button.ACTION);
});
game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.ACTION);
await new Promise<void>((resolve) => {
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.SUBMIT);
});
game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.ACTION);
});
game.onNextPrompt("SelectStarterPhase", Mode.SAVE_SLOT, () => {
const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler;
saveSlotSelectUiHandler.processInput(Button.ACTION);
resolve();
});
});
await game.phaseInterceptor.whenAboutToRun(EncounterPhase);
@ -429,15 +400,19 @@ describe("UI - Starter select", () => {
expect(game.scene.getParty()[0].variant).toBe(1);
}, 20000);
it("Bulbasaur - shiny - variant 1", async() => {
it("Bulbasaur - shiny - variant 2", async() => {
await game.importData("src/test/utils/saves/everything.prsv");
const caughtCount = Object.keys(game.scene.gameData.dexData).filter((key) => {
const species = game.scene.gameData.dexData[key];
return species.caughtAttr !== 0n;
}).length;
expect(caughtCount).toBe(Object.keys(allSpecies).length);
await game.runToTitle();
game.onNextPrompt("TitlePhase", Mode.TITLE, () => {
const currentPhase = game.scene.getCurrentPhase() as TitlePhase;
currentPhase.gameMode = GameModes.CLASSIC;
currentPhase.end();
});
await game.phaseInterceptor.mustRun(SelectStarterPhase).catch((error) => expect(error).toBe(SelectStarterPhase));
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.RIGHT);
@ -445,7 +420,9 @@ describe("UI - Starter select", () => {
handler.processInput(Button.V);
handler.processInput(Button.V);
handler.processInput(Button.ACTION);
game.phaseInterceptor.unlock();
});
await game.phaseInterceptor.run(SelectStarterPhase);
let options: OptionSelectItem[];
let optionSelectUiHandler: OptionSelectUiHandler;
await new Promise<void>((resolve) => {
@ -462,21 +439,20 @@ describe("UI - Starter select", () => {
expect(options.some(option => option.label === "Cancel")).toBe(true);
optionSelectUiHandler.processInput(Button.ACTION);
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.SUBMIT);
});
game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.ACTION);
});
game.onNextPrompt("SelectStarterPhase", Mode.SAVE_SLOT, () => {
const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler;
saveSlotSelectUiHandler.processInput(Button.ACTION);
});
game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.ACTION);
await new Promise<void>((resolve) => {
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.SUBMIT);
});
game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.ACTION);
});
game.onNextPrompt("SelectStarterPhase", Mode.SAVE_SLOT, () => {
const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler;
saveSlotSelectUiHandler.processInput(Button.ACTION);
resolve();
});
});
await game.phaseInterceptor.whenAboutToRun(EncounterPhase);
@ -485,15 +461,19 @@ describe("UI - Starter select", () => {
expect(game.scene.getParty()[0].variant).toBe(2);
}, 20000);
it("Check if first pokemon in party is caterpie from gen 1 and 1rd row, 3rd column ", async() => {
it("Check if first pokemon in party is caterpie from gen 1 and 1rd row, 3rd column", async() => {
await game.importData("src/test/utils/saves/everything.prsv");
const caughtCount = Object.keys(game.scene.gameData.dexData).filter((key) => {
const species = game.scene.gameData.dexData[key];
return species.caughtAttr !== 0n;
}).length;
expect(caughtCount).toBe(Object.keys(allSpecies).length);
await game.runToTitle();
game.onNextPrompt("TitlePhase", Mode.TITLE, () => {
const currentPhase = game.scene.getCurrentPhase() as TitlePhase;
currentPhase.gameMode = GameModes.CLASSIC;
currentPhase.end();
});
await game.phaseInterceptor.mustRun(SelectStarterPhase).catch((error) => expect(error).toBe(SelectStarterPhase));
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.RIGHT);
@ -501,7 +481,9 @@ describe("UI - Starter select", () => {
handler.processInput(Button.RIGHT);
handler.processInput(Button.RIGHT);
handler.processInput(Button.ACTION);
game.phaseInterceptor.unlock();
});
await game.phaseInterceptor.run(SelectStarterPhase);
let options: OptionSelectItem[];
let optionSelectUiHandler: OptionSelectUiHandler;
await new Promise<void>((resolve) => {
@ -526,6 +508,7 @@ describe("UI - Starter select", () => {
resolve();
});
});
expect(starterSelectUiHandler.starterGens[0]).toBe(0);
expect(starterSelectUiHandler.starterCursors[0]).toBe(3);
expect(starterSelectUiHandler.cursorObj.x).toBe(132 + 4 * 18);
@ -539,23 +522,23 @@ describe("UI - Starter select", () => {
const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler;
saveSlotSelectUiHandler.processInput(Button.ACTION);
});
game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.ACTION);
});
await game.phaseInterceptor.whenAboutToRun(EncounterPhase);
expect(game.scene.getParty()[0].species.speciesId).toBe(Species.CATERPIE);
}, 20000);
it("Check if first pokemon in party is nidoran_m from gen 1 and 2nd row, 4th column (cursor (9+4)-1) ", async() => {
it("Check if first pokemon in party is nidoran_m from gen 1 and 2nd row, 4th column (cursor (9+4)-1)", async() => {
await game.importData("src/test/utils/saves/everything.prsv");
const caughtCount = Object.keys(game.scene.gameData.dexData).filter((key) => {
const species = game.scene.gameData.dexData[key];
return species.caughtAttr !== 0n;
}).length;
expect(caughtCount).toBe(Object.keys(allSpecies).length);
await game.runToTitle();
game.onNextPrompt("TitlePhase", Mode.TITLE, () => {
const currentPhase = game.scene.getCurrentPhase() as TitlePhase;
currentPhase.gameMode = GameModes.CLASSIC;
currentPhase.end();
});
await game.phaseInterceptor.mustRun(SelectStarterPhase).catch((error) => expect(error).toBe(SelectStarterPhase));
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.RIGHT);
@ -564,7 +547,9 @@ describe("UI - Starter select", () => {
handler.processInput(Button.RIGHT);
handler.processInput(Button.DOWN);
handler.processInput(Button.ACTION);
game.phaseInterceptor.unlock();
});
await game.phaseInterceptor.run(SelectStarterPhase);
let options: OptionSelectItem[];
let optionSelectUiHandler: OptionSelectUiHandler;
await new Promise<void>((resolve) => {
@ -589,6 +574,7 @@ describe("UI - Starter select", () => {
resolve();
});
});
expect(starterSelectUiHandler.starterGens[0]).toBe(0);
expect(starterSelectUiHandler.starterCursors[0]).toBe(12);
expect(starterSelectUiHandler.cursorObj.x).toBe(132 + 4 * 18);
@ -602,10 +588,6 @@ describe("UI - Starter select", () => {
const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler;
saveSlotSelectUiHandler.processInput(Button.ACTION);
});
game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.ACTION);
});
await game.phaseInterceptor.whenAboutToRun(EncounterPhase);
expect(game.scene.getParty()[0].species.speciesId).toBe(Species.NIDORAN_M);
}, 20000);

View File

@ -0,0 +1,50 @@
export default class ErrorInterceptor {
private static instance: ErrorInterceptor;
public running;
constructor() {
this.running = [];
}
public static getInstance(): ErrorInterceptor {
if (!ErrorInterceptor.instance) {
ErrorInterceptor.instance = new ErrorInterceptor();
}
return ErrorInterceptor.instance;
}
clear() {
this.running = [];
}
add(obj) {
this.running.push(obj);
}
remove(obj) {
const index = this.running.indexOf(obj);
if (index !== -1) {
this.running.splice(index, 1);
}
}
}
process.on("uncaughtException", (error) => {
console.log(error);
const toStop = ErrorInterceptor.getInstance().running;
for (const elm of toStop) {
elm.rejectAll(error);
}
global.testFailed = true;
});
// Global error handler for unhandled promise rejections
process.on("unhandledRejection", (reason, promise) => {
console.log(reason);
const toStop = ErrorInterceptor.getInstance().running;
for (const elm of toStop) {
elm.rejectAll(reason);
}
global.testFailed = true;
});

View File

@ -2,21 +2,17 @@ import GameWrapper from "#app/test/utils/gameWrapper";
import {Mode} from "#app/ui/ui";
import {generateStarter, waitUntil} from "#app/test/utils/gameManagerUtils";
import {
CheckSwitchPhase,
CommandPhase,
EncounterPhase,
LoginPhase,
PostSummonPhase,
SelectGenderPhase,
SelectStarterPhase,
SummonPhase,
TitlePhase,
ToggleDoublePositionPhase,
} from "#app/phases";
import BattleScene from "#app/battle-scene.js";
import PhaseInterceptor from "#app/test/utils/phaseInterceptor";
import TextInterceptor from "#app/test/utils/TextInterceptor";
import {expect} from "vitest";
import {GameModes, getGameMode} from "#app/game-mode";
import fs from "fs";
import { AES, enc } from "crypto-js";
@ -26,6 +22,7 @@ import {PlayerGender} from "#app/data/enums/player-gender";
import {GameDataType} from "#app/data/enums/game-data-type";
import InputsHandler from "#app/test/utils/inputsHandler";
import {ExpNotification} from "#app/enums/exp-notification";
import ErrorInterceptor from "#app/test/utils/errorInterceptor";
/**
* Class to manage the game state and transitions between phases.
@ -43,6 +40,8 @@ export default class GameManager {
* @param bypassLogin - Whether to bypass the login phase.
*/
constructor(phaserGame: Phaser.Game, bypassLogin: boolean = true) {
localStorage.clear();
ErrorInterceptor.getInstance().clear();
BattleScene.prototype.randBattleSeedInt = (arg) => arg-1;
this.gameWrapper = new GameWrapper(phaserGame, bypassLogin);
this.scene = new BattleScene();
@ -94,14 +93,14 @@ export default class GameManager {
* @returns A promise that resolves when the title phase is reached.
*/
runToTitle(): Promise<void> {
return new Promise(async(resolve) => {
await this.phaseInterceptor.run(LoginPhase);
return new Promise(async(resolve, reject) => {
await this.phaseInterceptor.run(LoginPhase).catch((e) => reject(e));
this.onNextPrompt("SelectGenderPhase", Mode.OPTION_SELECT, () => {
this.scene.gameData.gender = PlayerGender.MALE;
this.endPhase();
}, () => this.isCurrentPhase(TitlePhase));
await this.phaseInterceptor.run(SelectGenderPhase, () => this.isCurrentPhase(TitlePhase));
await this.phaseInterceptor.run(TitlePhase);
await this.phaseInterceptor.run(SelectGenderPhase, () => this.isCurrentPhase(TitlePhase)).catch((e) => reject(e));
await this.phaseInterceptor.run(TitlePhase).catch((e) => reject(e));
this.scene.gameSpeed = 5;
this.scene.moveAnimations = false;
this.scene.showLevelUpStats = false;
@ -118,8 +117,8 @@ export default class GameManager {
* @returns A promise that resolves when the summon phase is reached.
*/
runToSummon(species?: Species[]): Promise<void> {
return new Promise(async(resolve) => {
await this.runToTitle();
return new Promise(async(resolve, reject) => {
await this.runToTitle().catch((e) => reject(e));
this.onNextPrompt("TitlePhase", Mode.TITLE, () => {
this.scene.gameMode = getGameMode(GameModes.CLASSIC);
const starters = generateStarter(this.scene, species);
@ -127,7 +126,7 @@ export default class GameManager {
this.scene.pushPhase(new EncounterPhase(this.scene, false));
selectStarterPhase.initBattle(starters);
});
await this.phaseInterceptor.run(EncounterPhase);
await this.phaseInterceptor.run(EncounterPhase).catch((e) => reject(e));
resolve();
});
}
@ -138,25 +137,18 @@ export default class GameManager {
* @returns A promise that resolves when the battle is started.
*/
startBattle(species?: Species[]): Promise<void> {
return new Promise(async(resolve) => {
await this.runToSummon(species);
await this.phaseInterceptor.runFrom(PostSummonPhase).to(ToggleDoublePositionPhase);
await this.phaseInterceptor.run(SummonPhase, () => this.isCurrentPhase(CheckSwitchPhase) || this.isCurrentPhase(PostSummonPhase));
return new Promise(async(resolve, reject) => {
await this.runToSummon(species).catch((e) => reject(e));
this.onNextPrompt("CheckSwitchPhase", Mode.CONFIRM, () => {
this.setMode(Mode.MESSAGE);
this.endPhase();
}, () => this.isCurrentPhase(PostSummonPhase));
}, () => this.isCurrentPhase(CommandPhase));
this.onNextPrompt("CheckSwitchPhase", Mode.CONFIRM, () => {
this.setMode(Mode.MESSAGE);
this.endPhase();
}, () => this.isCurrentPhase(PostSummonPhase));
await this.phaseInterceptor.run(CheckSwitchPhase, () => this.isCurrentPhase(PostSummonPhase));
await this.phaseInterceptor.run(CheckSwitchPhase, () => this.isCurrentPhase(PostSummonPhase));
await this.phaseInterceptor.runFrom(PostSummonPhase).to(CommandPhase);
await waitUntil(() => this.scene.ui?.getMode() === Mode.COMMAND);
}, () => this.isCurrentPhase(CommandPhase));
await this.phaseInterceptor.runFrom(PostSummonPhase).to(CommandPhase).catch((e) => reject(e));
console.log("==================[New Turn]==================");
expect(this.scene.ui?.getMode()).toBe(Mode.COMMAND);
expect(this.scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name);
return resolve();
});
}

View File

@ -86,7 +86,6 @@ export default class GameWrapper {
frames: {},
});
Pokemon.prototype.enableMask = () => null;
localStorage.clear();
}
setScene(scene: BattleScene) {

View File

@ -6,10 +6,12 @@ import {
LoginPhase, MessagePhase, MoveEffectPhase, MoveEndPhase, MovePhase, NewBattlePhase, NextEncounterPhase,
PostSummonPhase,
SelectGenderPhase, SelectModifierPhase,
SelectStarterPhase, ShinySparklePhase, ShowAbilityPhase, StatChangePhase, SummonPhase,
TitlePhase, ToggleDoublePositionPhase, TurnEndPhase, TurnInitPhase, TurnStartPhase, VictoryPhase
SelectStarterPhase, SelectTargetPhase, ShinySparklePhase, ShowAbilityPhase, StatChangePhase, SummonPhase,
TitlePhase, ToggleDoublePositionPhase, TurnEndPhase, TurnInitPhase, TurnStartPhase, UnavailablePhase, VictoryPhase
} from "#app/phases";
import {Mode} from "#app/ui/ui";
import UI, {Mode} from "#app/ui/ui";
import {Phase} from "#app/phase";
import ErrorInterceptor from "#app/test/utils/errorInterceptor";
export default class PhaseInterceptor {
public scene;
@ -21,6 +23,9 @@ export default class PhaseInterceptor {
private intervalRun;
private prompts;
private phaseFrom;
private inProgress;
private originalSetMode;
private originalSuperEnd;
/**
* List of phases with their corresponding start methods.
@ -56,6 +61,12 @@ export default class PhaseInterceptor {
[MoveEndPhase, this.startPhase],
[StatChangePhase, this.startPhase],
[ShinySparklePhase, this.startPhase],
[SelectTargetPhase, this.startPhase],
[UnavailablePhase, this.startPhase],
];
private endBySetMode = [
TitlePhase, SelectGenderPhase, CommandPhase, SelectModifierPhase
];
/**
@ -71,6 +82,15 @@ export default class PhaseInterceptor {
this.startPromptHander();
}
rejectAll(error) {
if (this.inProgress) {
clearInterval(this.promptInterval);
clearInterval(this.interval);
clearInterval(this.intervalRun);
this.inProgress.onError(error);
}
}
/**
* Method to set the starting phase.
* @param phaseFrom - The phase to start from.
@ -86,20 +106,31 @@ export default class PhaseInterceptor {
* @param phaseTo - The phase to transition to.
* @returns A promise that resolves when the transition is complete.
*/
async to(phaseTo): Promise<void> {
return new Promise(async (resolve) => {
await this.run(this.phaseFrom);
async to(phaseTo, runTarget: boolean = true): Promise<void> {
return new Promise(async (resolve, reject) => {
ErrorInterceptor.getInstance().add(this);
await this.run(this.phaseFrom).catch((e) => reject(e));
this.phaseFrom = null;
const targetName = typeof phaseTo === "string" ? phaseTo : phaseTo.name;
this.intervalRun = setInterval(async () => {
this.intervalRun = setInterval(async() => {
const currentPhase = this.onHold?.length && this.onHold[0];
if (currentPhase && currentPhase.name !== targetName) {
await this.run(currentPhase.name);
} else if (currentPhase.name === targetName) {
await this.run(currentPhase.name);
if (currentPhase && currentPhase.name === targetName) {
clearInterval(this.intervalRun);
if (!runTarget) {
return resolve();
}
await this.run(currentPhase).catch((e) => {
clearInterval(this.intervalRun);
return reject(e);
});
return resolve();
}
if (currentPhase && currentPhase.name !== targetName) {
await this.run(currentPhase).catch((e) => {
clearInterval(this.intervalRun);
return reject(e);
});
}
});
});
}
@ -111,92 +142,53 @@ export default class PhaseInterceptor {
* @returns A promise that resolves when the phase is run.
*/
run(phaseTarget, skipFn?): Promise<void> {
this.scene.moveAnimations = null; // Mandatory to avoid crash
return new Promise(async (resolve) => {
this.waitUntil(phaseTarget, skipFn).then(() => {
const currentPhase = this.onHold.shift();
currentPhase.call();
resolve();
}).catch(() => {
resolve();
});
});
}
/**
* Method to ensure a phase is run, to throw error on test if not.
* @param phaseTarget - The phase to run.
* @returns A promise that resolves when the phase is run.
*/
mustRun(phaseTarget): Promise<void> {
const targetName = typeof phaseTarget === "string" ? phaseTarget : phaseTarget.name;
this.scene.moveAnimations = null; // Mandatory to avoid crash
return new Promise(async (resolve, reject) => {
ErrorInterceptor.getInstance().add(this);
const interval = setInterval(async () => {
const currentPhase = this.onHold?.length && this.onHold[0];
if (currentPhase && currentPhase.name !== targetName) {
reject(currentPhase);
} else if (currentPhase && currentPhase.name === targetName) {
const currentPhase = this.onHold.shift();
if (currentPhase) {
if (currentPhase.name !== targetName) {
clearInterval(interval);
const skip = skipFn && skipFn(currentPhase.name);
if (skip) {
this.onHold.unshift(currentPhase);
ErrorInterceptor.getInstance().remove(this);
return resolve();
}
clearInterval(interval);
return reject(`Wrong phase: this is ${currentPhase.name} and not ${targetName}`);
}
clearInterval(interval);
await this.run(phaseTarget);
resolve();
this.inProgress = {
name: currentPhase.name,
callback: () => {
ErrorInterceptor.getInstance().remove(this);
resolve();
},
onError: (error) => reject(error),
};
currentPhase.call();
}
});
});
}
/**
* Method to execute actions when about to run a phase. Does not run the phase, stop right before.
* @param phaseTarget - The phase to run.
* @param skipFn - Optional skip function.
* @returns A promise that resolves when the phase is about to run.
*/
whenAboutToRun(phaseTarget, skipFn?): Promise<void> {
return new Promise(async (resolve) => {
this.waitUntil(phaseTarget, skipFn).then(() => {
resolve();
}).catch(() => {
resolve();
});
});
}
/**
* Method to remove a phase from the list.
* @param phaseTarget - The phase to remove.
* @param skipFn - Optional skip function.
* @returns A promise that resolves when the phase is removed.
*/
remove(phaseTarget, skipFn?): Promise<void> {
return new Promise(async (resolve) => {
this.waitUntil(phaseTarget, skipFn).then(() => {
this.onHold.shift();
this.scene.getCurrentPhase().end();
resolve();
}).catch(() => {
resolve();
});
});
}
/**
* Method to wait until a specific phase is reached.
* @param phaseTarget - The phase to wait for.
* @param skipFn - Optional skip function.
* @returns A promise that resolves when the phase is reached.
*/
waitUntil(phaseTarget, skipFn?): Promise<void> {
const targetName = typeof phaseTarget === "string" ? phaseTarget : phaseTarget.name;
return new Promise((resolve, reject) => {
this.interval = setInterval(() => {
const currentPhase = this.onHold?.length && this.onHold[0] && this.onHold[0].name;
// if the currentPhase here is not filled, it means it's a phase we haven't added to the list
if (currentPhase === targetName) {
clearInterval(this.interval);
return resolve();
} else if (skipFn && skipFn()) {
clearInterval(this.interval);
return reject("Skipped phase");
this.scene.moveAnimations = null; // Mandatory to avoid crash
return new Promise(async (resolve, reject) => {
ErrorInterceptor.getInstance().add(this);
const interval = setInterval(async () => {
const currentPhase = this.onHold.shift();
if (currentPhase) {
if (currentPhase.name !== targetName) {
this.onHold.unshift(currentPhase);
} else {
clearInterval(interval);
resolve();
}
}
});
});
@ -206,10 +198,17 @@ export default class PhaseInterceptor {
* Method to initialize phases and their corresponding methods.
*/
initPhases() {
for (const [phase, method] of this.PHASES) {
this.originalSetMode = UI.prototype.setMode;
this.originalSuperEnd = Phase.prototype.end;
UI.prototype.setMode = (mode, ...args) => this.setMode.call(this, mode, ...args);
Phase.prototype.end = () => this.superEndPhase.call(this);
for (const [phase, methodStart] of this.PHASES) {
const originalStart = phase.prototype.start;
this.phases[phase.name] = originalStart;
phase.prototype.start = () => method.call(this, phase);
this.phases[phase.name] = {
start: originalStart,
endBySetMode: this.endBySetMode.some((elm) => elm.name === phase.name),
};
phase.prototype.start = () => methodStart.call(this, phase);
}
}
@ -223,11 +222,44 @@ export default class PhaseInterceptor {
this.onHold.push({
name: phase.name,
call: () => {
this.phases[phase.name].apply(instance);
this.phases[phase.name].start.apply(instance);
}
});
}
unlock() {
this.inProgress?.callback();
this.inProgress = undefined;
}
/**
* Method to end a phase and log it.
* @param phase - The phase to start.
*/
superEndPhase() {
const instance = this.scene.getCurrentPhase();
console.log(`%c INTERCEPTED Super End Phase ${instance.constructor.name}`, "color:red;");
this.originalSuperEnd.apply(instance);
this.inProgress?.callback();
this.inProgress = undefined;
}
/**
* m2m to set mode.
* @param phase - The phase to start.
*/
setMode(mode: Mode, ...args: any[]): Promise<void> {
const currentPhase = this.scene.getCurrentPhase();
const instance = this.scene.ui;
console.log("setMode", mode, args);
const ret = this.originalSetMode.apply(instance, [mode, ...args]);
if (this.phases[currentPhase.constructor.name].endBySetMode) {
this.inProgress?.callback();
this.inProgress = undefined;
}
return ret;
}
/**
* Method to start the prompt handler.
*/
@ -271,9 +303,12 @@ export default class PhaseInterceptor {
*/
restoreOg() {
for (const [phase] of this.PHASES) {
phase.prototype.start = this.phases[phase.name];
phase.prototype.start = this.phases[phase.name].start;
}
UI.prototype.setMode = this.originalSetMode;
Phase.prototype.end = this.originalSuperEnd;
clearInterval(this.promptInterval);
clearInterval(this.interval);
clearInterval(this.intervalRun);
}
}

View File

@ -21,3 +21,5 @@ initPokemonForms();
initSpecies();
initMoves();
initAbilities();
global.testFailed = false;