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

View File

@ -5,7 +5,7 @@ import * as overrides from "#app/overrides";
import {Abilities} from "#app/data/enums/abilities"; import {Abilities} from "#app/data/enums/abilities";
import {Species} from "#app/data/enums/species"; import {Species} from "#app/data/enums/species";
import { import {
CommandPhase, EnemyCommandPhase, CommandPhase, EnemyCommandPhase, SelectTargetPhase,
TurnStartPhase TurnStartPhase
} from "#app/phases"; } from "#app/phases";
import {Mode} from "#app/ui/ui"; import {Mode} from "#app/ui/ui";
@ -55,7 +55,6 @@ describe("Battle order", () => {
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false); (game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false);
}); });
await game.phaseInterceptor.run(EnemyCommandPhase); await game.phaseInterceptor.run(EnemyCommandPhase);
await game.phaseInterceptor.whenAboutToRun(TurnStartPhase);
const phase = game.scene.getCurrentPhase() as TurnStartPhase; const phase = game.scene.getCurrentPhase() as TurnStartPhase;
const order = phase.getOrder(); const order = phase.getOrder();
expect(order[0]).toBe(2); expect(order[0]).toBe(2);
@ -77,7 +76,6 @@ describe("Battle order", () => {
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false); (game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false);
}); });
await game.phaseInterceptor.run(EnemyCommandPhase); await game.phaseInterceptor.run(EnemyCommandPhase);
await game.phaseInterceptor.whenAboutToRun(TurnStartPhase);
const phase = game.scene.getCurrentPhase() as TurnStartPhase; const phase = game.scene.getCurrentPhase() as TurnStartPhase;
const order = phase.getOrder(); const order = phase.getOrder();
expect(order[0]).toBe(0); expect(order[0]).toBe(0);
@ -118,9 +116,7 @@ describe("Battle order", () => {
const handler = game.scene.ui.getHandler() as TargetSelectUiHandler; const handler = game.scene.ui.getHandler() as TargetSelectUiHandler;
handler.processInput(Button.ACTION); handler.processInput(Button.ACTION);
}); });
await game.phaseInterceptor.runFrom(CommandPhase).to(EnemyCommandPhase); await game.phaseInterceptor.runFrom(SelectTargetPhase).to(TurnStartPhase, false);
await game.phaseInterceptor.run(EnemyCommandPhase);
await game.phaseInterceptor.whenAboutToRun(TurnStartPhase);
const phase = game.scene.getCurrentPhase() as TurnStartPhase; const phase = game.scene.getCurrentPhase() as TurnStartPhase;
const order = phase.getOrder(); const order = phase.getOrder();
expect(order.indexOf(0)).toBeGreaterThan(order.indexOf(2)); expect(order.indexOf(0)).toBeGreaterThan(order.indexOf(2));
@ -163,9 +159,7 @@ describe("Battle order", () => {
const handler = game.scene.ui.getHandler() as TargetSelectUiHandler; const handler = game.scene.ui.getHandler() as TargetSelectUiHandler;
handler.processInput(Button.ACTION); handler.processInput(Button.ACTION);
}); });
await game.phaseInterceptor.runFrom(CommandPhase).to(EnemyCommandPhase); await game.phaseInterceptor.runFrom(SelectTargetPhase).to(TurnStartPhase, false);
await game.phaseInterceptor.run(EnemyCommandPhase);
await game.phaseInterceptor.whenAboutToRun(TurnStartPhase);
const phase = game.scene.getCurrentPhase() as TurnStartPhase; const phase = game.scene.getCurrentPhase() as TurnStartPhase;
const order = phase.getOrder(); const order = phase.getOrder();
expect(order.indexOf(3)).toBeLessThan(order.indexOf(0)); expect(order.indexOf(3)).toBeLessThan(order.indexOf(0));
@ -207,9 +201,7 @@ describe("Battle order", () => {
const handler = game.scene.ui.getHandler() as TargetSelectUiHandler; const handler = game.scene.ui.getHandler() as TargetSelectUiHandler;
handler.processInput(Button.ACTION); handler.processInput(Button.ACTION);
}); });
await game.phaseInterceptor.runFrom(CommandPhase).to(EnemyCommandPhase); await game.phaseInterceptor.runFrom(SelectTargetPhase).to(TurnStartPhase, false);
await game.phaseInterceptor.run(EnemyCommandPhase);
await game.phaseInterceptor.whenAboutToRun(TurnStartPhase);
const phase = game.scene.getCurrentPhase() as TurnStartPhase; const phase = game.scene.getCurrentPhase() as TurnStartPhase;
const order = phase.getOrder(); const order = phase.getOrder();
expect(order.indexOf(1)).toBeLessThan(order.indexOf(0)); 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 {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 {Mode} from "#app/ui/ui";
import {GameModes} from "#app/game-mode"; import {GameModes} from "#app/game-mode";
import {Species} from "#app/data/enums/species"; import {Species} from "#app/data/enums/species";
import * as overrides from "../../overrides"; import * as overrides from "../../overrides";
import {Command} from "#app/ui/command-ui-handler"; import {Command} from "#app/ui/command-ui-handler";
import { import {
BattleEndPhase,
BerryPhase,
CommandPhase, CommandPhase,
DamagePhase,
EggLapsePhase,
EncounterPhase, EncounterPhase,
EnemyCommandPhase, EnemyCommandPhase,
FaintPhase,
LoginPhase, LoginPhase,
MessagePhase,
MoveEffectPhase,
MoveEndPhase,
MovePhase,
PostSummonPhase,
SelectGenderPhase, SelectGenderPhase,
SelectModifierPhase, SelectModifierPhase,
SelectStarterPhase, SelectStarterPhase,
StatChangePhase, SummonPhase,
TitlePhase, TitlePhase,
TurnEndPhase,
TurnInitPhase, TurnInitPhase,
TurnStartPhase,
VictoryPhase,
} from "#app/phases"; } from "#app/phases";
import {Moves} from "#app/data/enums/moves"; import {Moves} from "#app/data/enums/moves";
import GameManager from "#app/test/utils/gameManager"; import GameManager from "#app/test/utils/gameManager";
@ -36,6 +23,7 @@ import Phaser from "phaser";
import {allSpecies} from "#app/data/pokemon-species"; import {allSpecies} from "#app/data/pokemon-species";
import {PlayerGender} from "#app/data/enums/player-gender"; import {PlayerGender} from "#app/data/enums/player-gender";
import { getGameMode } from "#app/game-mode.js"; import { getGameMode } from "#app/game-mode.js";
import {Abilities} from "#app/data/enums/abilities";
describe("Test Battle Phase", () => { describe("Test Battle Phase", () => {
let phaserGame: Phaser.Game; let phaserGame: Phaser.Game;
@ -55,22 +43,6 @@ describe("Test Battle Phase", () => {
game = new GameManager(phaserGame); 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() => { it("test phase interceptor with prompt", async() => {
await game.phaseInterceptor.run(LoginPhase); await game.phaseInterceptor.run(LoginPhase);
@ -87,7 +59,7 @@ describe("Test Battle Phase", () => {
expect(game.scene.ui?.getMode()).toBe(Mode.TITLE); expect(game.scene.ui?.getMode()).toBe(Mode.TITLE);
expect(game.scene.gameData.gender).toBe(PlayerGender.MALE); expect(game.scene.gameData.gender).toBe(PlayerGender.MALE);
}, 100000); }, 20000);
it("test phase interceptor with prompt with preparation for a future prompt", async() => { it("test phase interceptor with prompt with preparation for a future prompt", async() => {
await game.phaseInterceptor.run(LoginPhase); await game.phaseInterceptor.run(LoginPhase);
@ -109,13 +81,13 @@ describe("Test Battle Phase", () => {
expect(game.scene.ui?.getMode()).toBe(Mode.TITLE); expect(game.scene.ui?.getMode()).toBe(Mode.TITLE);
expect(game.scene.gameData.gender).toBe(PlayerGender.MALE); expect(game.scene.gameData.gender).toBe(PlayerGender.MALE);
}, 100000); }, 20000);
it("newGame one-liner", async() => { it("newGame one-liner", async() => {
await game.startBattle(); await game.startBattle();
expect(game.scene.ui?.getMode()).toBe(Mode.COMMAND); expect(game.scene.ui?.getMode()).toBe(Mode.COMMAND);
expect(game.scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name); expect(game.scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name);
}, 100000); }, 20000);
it("do attack wave 3 - single battle - regular - OHKO", async() => { it("do attack wave 3 - single battle - regular - OHKO", async() => {
vi.spyOn(overrides, "STARTER_SPECIES_OVERRIDE", "get").mockReturnValue(Species.MEWTWO); 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_LEVEL_OVERRIDE", "get").mockReturnValue(2000);
vi.spyOn(overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(3); vi.spyOn(overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(3);
vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE]); 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); vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
await game.startBattle(); await game.startBattle();
game.onNextPrompt("CommandPhase", Mode.COMMAND, () => { game.onNextPrompt("CommandPhase", Mode.COMMAND, () => {
@ -132,28 +106,10 @@ describe("Test Battle Phase", () => {
const movePosition = getMovePosition(game.scene, 0, Moves.TACKLE); const movePosition = getMovePosition(game.scene, 0, Moves.TACKLE);
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false); (game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false);
}); });
await game.phaseInterceptor.run(EnemyCommandPhase); await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(SelectModifierPhase);
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);
expect(game.scene.ui?.getMode()).toBe(Mode.MODIFIER_SELECT); expect(game.scene.ui?.getMode()).toBe(Mode.MODIFIER_SELECT);
expect(game.scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name); 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() => { 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); 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_LEVEL_OVERRIDE", "get").mockReturnValue(5);
vi.spyOn(overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(3); vi.spyOn(overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(3);
vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE]); 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); vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
await game.startBattle(); await game.startBattle();
game.onNextPrompt("CommandPhase", Mode.COMMAND, () => { game.onNextPrompt("CommandPhase", Mode.COMMAND, () => {
@ -171,35 +128,8 @@ describe("Test Battle Phase", () => {
const movePosition = getMovePosition(game.scene, 0, Moves.TACKLE); const movePosition = getMovePosition(game.scene, 0, Moves.TACKLE);
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false); (game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false);
}); });
await game.phaseInterceptor.run(EnemyCommandPhase); await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(TurnInitPhase);
await game.phaseInterceptor.run(TurnStartPhase); }, 20000);
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);
it("load 100% data file", async() => { it("load 100% data file", async() => {
await game.importData("src/test/utils/saves/everything.prsv"); await game.importData("src/test/utils/saves/everything.prsv");
@ -208,7 +138,7 @@ describe("Test Battle Phase", () => {
return species.caughtAttr !== 0n; return species.caughtAttr !== 0n;
}).length; }).length;
expect(caughtCount).toBe(Object.keys(allSpecies).length); expect(caughtCount).toBe(Object.keys(allSpecies).length);
}, 50000); }, 20000);
it("start battle with selected team", async() => { it("start battle with selected team", async() => {
await game.startBattle([ 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()[0].species.speciesId).toBe(Species.CHARIZARD);
expect(game.scene.getParty()[1].species.speciesId).toBe(Species.CHANSEY); expect(game.scene.getParty()[1].species.speciesId).toBe(Species.CHANSEY);
expect(game.scene.getParty()[2].species.speciesId).toBe(Species.MEW); expect(game.scene.getParty()[2].species.speciesId).toBe(Species.MEW);
}, 50000); }, 20000);
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);
it("test remove random battle seed int", async() => { it("test remove random battle seed int", async() => {
for (let i=0; i<10; i++) { for (let i=0; i<10; i++) {
@ -246,5 +157,107 @@ describe("Test Battle Phase", () => {
expect(rand).toBe(14); 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", () => { describe("LoginPhase", () => {
it("should start the login phase", async () => { it("should start the login phase", async () => {
const loginPhase = new LoginPhase(scene); const loginPhase = new LoginPhase(scene);
loginPhase.start(); scene.pushPhase(loginPhase);
await game.phaseInterceptor.run(LoginPhase);
expect(scene.ui.getMode()).to.equal(Mode.MESSAGE); expect(scene.ui.getMode()).to.equal(Mode.MESSAGE);
}); });
}); });
@ -36,16 +37,18 @@ describe("Phases", () => {
describe("TitlePhase", () => { describe("TitlePhase", () => {
it("should start the title phase", async () => { it("should start the title phase", async () => {
const titlePhase = new TitlePhase(scene); const titlePhase = new TitlePhase(scene);
titlePhase.start(); scene.pushPhase(titlePhase);
expect(scene.ui.getMode()).to.equal(Mode.MESSAGE); await game.phaseInterceptor.run(TitlePhase);
expect(scene.ui.getMode()).to.equal(Mode.TITLE);
}); });
}); });
describe("UnavailablePhase", () => { describe("UnavailablePhase", () => {
it("should start the unavailable phase", async () => { it("should start the unavailable phase", async () => {
const unavailablePhase = new UnavailablePhase(scene); const unavailablePhase = new UnavailablePhase(scene);
unavailablePhase.start(); scene.pushPhase(unavailablePhase);
await game.phaseInterceptor.run(UnavailablePhase);
expect(scene.ui.getMode()).to.equal(Mode.UNAVAILABLE); 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 SaveSlotSelectUiHandler from "#app/ui/save-slot-select-ui-handler";
import {OptionSelectItem} from "#app/ui/abstact-option-select-ui-handler"; import {OptionSelectItem} from "#app/ui/abstact-option-select-ui-handler";
import {Gender} from "#app/data/gender"; import {Gender} from "#app/data/gender";
import {allSpecies} from "#app/data/pokemon-species";
import {Nature} from "#app/data/nature"; import {Nature} from "#app/data/nature";
import {Abilities} from "#app/data/enums/abilities"; import {Abilities} from "#app/data/enums/abilities";
import {allSpecies} from "#app/data/pokemon-species";
describe("UI - Starter select", () => { describe("UI - Starter select", () => {
@ -51,12 +51,13 @@ describe("UI - Starter select", () => {
currentPhase.gameMode = GameModes.CLASSIC; currentPhase.gameMode = GameModes.CLASSIC;
currentPhase.end(); currentPhase.end();
}); });
await game.phaseInterceptor.mustRun(SelectStarterPhase).catch((error) => expect(error).toBe(SelectStarterPhase));
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => { game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.RIGHT); handler.processInput(Button.RIGHT);
handler.processInput(Button.ACTION); handler.processInput(Button.ACTION);
game.phaseInterceptor.unlock();
}); });
await game.phaseInterceptor.run(SelectStarterPhase);
let options: OptionSelectItem[]; let options: OptionSelectItem[];
let optionSelectUiHandler: OptionSelectUiHandler; let optionSelectUiHandler: OptionSelectUiHandler;
await new Promise<void>((resolve) => { await new Promise<void>((resolve) => {
@ -73,6 +74,7 @@ describe("UI - Starter select", () => {
expect(options.some(option => option.label === "Cancel")).toBe(true); expect(options.some(option => option.label === "Cancel")).toBe(true);
optionSelectUiHandler.processInput(Button.ACTION); optionSelectUiHandler.processInput(Button.ACTION);
await new Promise<void>((resolve) => {
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => { game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.SUBMIT); handler.processInput(Button.SUBMIT);
@ -84,10 +86,8 @@ describe("UI - Starter select", () => {
game.onNextPrompt("SelectStarterPhase", Mode.SAVE_SLOT, () => { game.onNextPrompt("SelectStarterPhase", Mode.SAVE_SLOT, () => {
const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler; const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler;
saveSlotSelectUiHandler.processInput(Button.ACTION); saveSlotSelectUiHandler.processInput(Button.ACTION);
resolve();
}); });
game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.ACTION);
}); });
await game.phaseInterceptor.whenAboutToRun(EncounterPhase); await game.phaseInterceptor.whenAboutToRun(EncounterPhase);
@ -99,19 +99,25 @@ describe("UI - Starter select", () => {
it("Bulbasaur - shiny - variant 2 female hardy overgrow", async() => { it("Bulbasaur - shiny - variant 2 female hardy overgrow", async() => {
await game.importData("src/test/utils/saves/everything.prsv"); 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(); await game.runToTitle();
game.onNextPrompt("TitlePhase", Mode.TITLE, () => { game.onNextPrompt("TitlePhase", Mode.TITLE, () => {
const currentPhase = game.scene.getCurrentPhase() as TitlePhase; const currentPhase = game.scene.getCurrentPhase() as TitlePhase;
currentPhase.gameMode = GameModes.CLASSIC; currentPhase.gameMode = GameModes.CLASSIC;
currentPhase.end(); currentPhase.end();
}); });
await game.phaseInterceptor.mustRun(SelectStarterPhase).catch((error) => expect(error).toBe(SelectStarterPhase));
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => { game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.RIGHT); handler.processInput(Button.RIGHT);
handler.processInput(Button.CYCLE_GENDER); handler.processInput(Button.CYCLE_GENDER);
handler.processInput(Button.ACTION); handler.processInput(Button.ACTION);
game.phaseInterceptor.unlock();
}); });
await game.phaseInterceptor.run(SelectStarterPhase);
let options: OptionSelectItem[]; let options: OptionSelectItem[];
let optionSelectUiHandler: OptionSelectUiHandler; let optionSelectUiHandler: OptionSelectUiHandler;
await new Promise<void>((resolve) => { await new Promise<void>((resolve) => {
@ -128,6 +134,7 @@ describe("UI - Starter select", () => {
expect(options.some(option => option.label === "Cancel")).toBe(true); expect(options.some(option => option.label === "Cancel")).toBe(true);
optionSelectUiHandler.processInput(Button.ACTION); optionSelectUiHandler.processInput(Button.ACTION);
await new Promise<void>((resolve) => {
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => { game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.SUBMIT); handler.processInput(Button.SUBMIT);
@ -139,10 +146,8 @@ describe("UI - Starter select", () => {
game.onNextPrompt("SelectStarterPhase", Mode.SAVE_SLOT, () => { game.onNextPrompt("SelectStarterPhase", Mode.SAVE_SLOT, () => {
const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler; const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler;
saveSlotSelectUiHandler.processInput(Button.ACTION); saveSlotSelectUiHandler.processInput(Button.ACTION);
resolve();
}); });
game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.ACTION);
}); });
await game.phaseInterceptor.whenAboutToRun(EncounterPhase); await game.phaseInterceptor.whenAboutToRun(EncounterPhase);
@ -153,15 +158,19 @@ describe("UI - Starter select", () => {
expect(game.scene.getParty()[0].getAbility().id).toBe(Abilities.OVERGROW); expect(game.scene.getParty()[0].getAbility().id).toBe(Abilities.OVERGROW);
}, 20000); }, 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"); 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(); await game.runToTitle();
game.onNextPrompt("TitlePhase", Mode.TITLE, () => { game.onNextPrompt("TitlePhase", Mode.TITLE, () => {
const currentPhase = game.scene.getCurrentPhase() as TitlePhase; const currentPhase = game.scene.getCurrentPhase() as TitlePhase;
currentPhase.gameMode = GameModes.CLASSIC; currentPhase.gameMode = GameModes.CLASSIC;
currentPhase.end(); currentPhase.end();
}); });
await game.phaseInterceptor.mustRun(SelectStarterPhase).catch((error) => expect(error).toBe(SelectStarterPhase));
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => { game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.RIGHT); handler.processInput(Button.RIGHT);
@ -169,7 +178,9 @@ describe("UI - Starter select", () => {
handler.processInput(Button.CYCLE_NATURE); handler.processInput(Button.CYCLE_NATURE);
handler.processInput(Button.CYCLE_ABILITY); handler.processInput(Button.CYCLE_ABILITY);
handler.processInput(Button.ACTION); handler.processInput(Button.ACTION);
game.phaseInterceptor.unlock();
}); });
await game.phaseInterceptor.run(SelectStarterPhase);
let options: OptionSelectItem[]; let options: OptionSelectItem[];
let optionSelectUiHandler: OptionSelectUiHandler; let optionSelectUiHandler: OptionSelectUiHandler;
await new Promise<void>((resolve) => { await new Promise<void>((resolve) => {
@ -186,6 +197,7 @@ describe("UI - Starter select", () => {
expect(options.some(option => option.label === "Cancel")).toBe(true); expect(options.some(option => option.label === "Cancel")).toBe(true);
optionSelectUiHandler.processInput(Button.ACTION); optionSelectUiHandler.processInput(Button.ACTION);
await new Promise<void>((resolve) => {
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => { game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.SUBMIT); handler.processInput(Button.SUBMIT);
@ -197,10 +209,8 @@ describe("UI - Starter select", () => {
game.onNextPrompt("SelectStarterPhase", Mode.SAVE_SLOT, () => { game.onNextPrompt("SelectStarterPhase", Mode.SAVE_SLOT, () => {
const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler; const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler;
saveSlotSelectUiHandler.processInput(Button.ACTION); saveSlotSelectUiHandler.processInput(Button.ACTION);
resolve();
}); });
game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.ACTION);
}); });
await game.phaseInterceptor.whenAboutToRun(EncounterPhase); await game.phaseInterceptor.whenAboutToRun(EncounterPhase);
@ -211,21 +221,27 @@ describe("UI - Starter select", () => {
expect(game.scene.getParty()[0].getAbility().id).toBe(Abilities.CHLOROPHYLL); expect(game.scene.getParty()[0].getAbility().id).toBe(Abilities.CHLOROPHYLL);
}, 20000); }, 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"); 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(); await game.runToTitle();
game.onNextPrompt("TitlePhase", Mode.TITLE, () => { game.onNextPrompt("TitlePhase", Mode.TITLE, () => {
const currentPhase = game.scene.getCurrentPhase() as TitlePhase; const currentPhase = game.scene.getCurrentPhase() as TitlePhase;
currentPhase.gameMode = GameModes.CLASSIC; currentPhase.gameMode = GameModes.CLASSIC;
currentPhase.end(); currentPhase.end();
}); });
await game.phaseInterceptor.mustRun(SelectStarterPhase).catch((error) => expect(error).toBe(SelectStarterPhase));
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => { game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.RIGHT); handler.processInput(Button.RIGHT);
handler.processInput(Button.CYCLE_GENDER); handler.processInput(Button.CYCLE_GENDER);
handler.processInput(Button.ACTION); handler.processInput(Button.ACTION);
game.phaseInterceptor.unlock();
}); });
await game.phaseInterceptor.run(SelectStarterPhase);
let options: OptionSelectItem[]; let options: OptionSelectItem[];
let optionSelectUiHandler: OptionSelectUiHandler; let optionSelectUiHandler: OptionSelectUiHandler;
await new Promise<void>((resolve) => { await new Promise<void>((resolve) => {
@ -242,6 +258,7 @@ describe("UI - Starter select", () => {
expect(options.some(option => option.label === "Cancel")).toBe(true); expect(options.some(option => option.label === "Cancel")).toBe(true);
optionSelectUiHandler.processInput(Button.ACTION); optionSelectUiHandler.processInput(Button.ACTION);
await new Promise<void>((resolve) => {
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => { game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.SUBMIT); handler.processInput(Button.SUBMIT);
@ -253,10 +270,8 @@ describe("UI - Starter select", () => {
game.onNextPrompt("SelectStarterPhase", Mode.SAVE_SLOT, () => { game.onNextPrompt("SelectStarterPhase", Mode.SAVE_SLOT, () => {
const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler; const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler;
saveSlotSelectUiHandler.processInput(Button.ACTION); saveSlotSelectUiHandler.processInput(Button.ACTION);
resolve();
}); });
game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.ACTION);
}); });
await game.phaseInterceptor.whenAboutToRun(EncounterPhase); await game.phaseInterceptor.whenAboutToRun(EncounterPhase);
@ -268,19 +283,25 @@ describe("UI - Starter select", () => {
it("Bulbasaur - not shiny", async() => { it("Bulbasaur - not shiny", async() => {
await game.importData("src/test/utils/saves/everything.prsv"); 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(); await game.runToTitle();
game.onNextPrompt("TitlePhase", Mode.TITLE, () => { game.onNextPrompt("TitlePhase", Mode.TITLE, () => {
const currentPhase = game.scene.getCurrentPhase() as TitlePhase; const currentPhase = game.scene.getCurrentPhase() as TitlePhase;
currentPhase.gameMode = GameModes.CLASSIC; currentPhase.gameMode = GameModes.CLASSIC;
currentPhase.end(); currentPhase.end();
}); });
await game.phaseInterceptor.mustRun(SelectStarterPhase).catch((error) => expect(error).toBe(SelectStarterPhase));
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => { game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.RIGHT); handler.processInput(Button.RIGHT);
handler.processInput(Button.CYCLE_SHINY); handler.processInput(Button.CYCLE_SHINY);
handler.processInput(Button.ACTION); handler.processInput(Button.ACTION);
game.phaseInterceptor.unlock();
}); });
await game.phaseInterceptor.run(SelectStarterPhase);
let options: OptionSelectItem[]; let options: OptionSelectItem[];
let optionSelectUiHandler: OptionSelectUiHandler; let optionSelectUiHandler: OptionSelectUiHandler;
await new Promise<void>((resolve) => { await new Promise<void>((resolve) => {
@ -297,6 +318,7 @@ describe("UI - Starter select", () => {
expect(options.some(option => option.label === "Cancel")).toBe(true); expect(options.some(option => option.label === "Cancel")).toBe(true);
optionSelectUiHandler.processInput(Button.ACTION); optionSelectUiHandler.processInput(Button.ACTION);
await new Promise<void>((resolve) => {
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => { game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.SUBMIT); handler.processInput(Button.SUBMIT);
@ -308,10 +330,8 @@ describe("UI - Starter select", () => {
game.onNextPrompt("SelectStarterPhase", Mode.SAVE_SLOT, () => { game.onNextPrompt("SelectStarterPhase", Mode.SAVE_SLOT, () => {
const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler; const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler;
saveSlotSelectUiHandler.processInput(Button.ACTION); saveSlotSelectUiHandler.processInput(Button.ACTION);
resolve();
}); });
game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.ACTION);
}); });
await game.phaseInterceptor.whenAboutToRun(EncounterPhase); await game.phaseInterceptor.whenAboutToRun(EncounterPhase);
@ -320,76 +340,28 @@ describe("UI - Starter select", () => {
expect(game.scene.getParty()[0].variant).toBe(0); expect(game.scene.getParty()[0].variant).toBe(0);
}, 20000); }, 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() => { it("Bulbasaur - shiny - variant 1", async() => {
await game.importData("src/test/utils/saves/everything.prsv"); 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(); await game.runToTitle();
game.onNextPrompt("TitlePhase", Mode.TITLE, () => { game.onNextPrompt("TitlePhase", Mode.TITLE, () => {
const currentPhase = game.scene.getCurrentPhase() as TitlePhase; const currentPhase = game.scene.getCurrentPhase() as TitlePhase;
currentPhase.gameMode = GameModes.CLASSIC; currentPhase.gameMode = GameModes.CLASSIC;
currentPhase.end(); currentPhase.end();
}); });
await game.phaseInterceptor.mustRun(SelectStarterPhase).catch((error) => expect(error).toBe(SelectStarterPhase));
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => { game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.RIGHT); handler.processInput(Button.RIGHT);
handler.processInput(Button.V); handler.processInput(Button.V);
handler.processInput(Button.V); handler.processInput(Button.V);
handler.processInput(Button.ACTION); handler.processInput(Button.ACTION);
game.phaseInterceptor.unlock();
}); });
await game.phaseInterceptor.run(SelectStarterPhase);
let options: OptionSelectItem[]; let options: OptionSelectItem[];
let optionSelectUiHandler: OptionSelectUiHandler; let optionSelectUiHandler: OptionSelectUiHandler;
await new Promise<void>((resolve) => { await new Promise<void>((resolve) => {
@ -406,6 +378,7 @@ describe("UI - Starter select", () => {
expect(options.some(option => option.label === "Cancel")).toBe(true); expect(options.some(option => option.label === "Cancel")).toBe(true);
optionSelectUiHandler.processInput(Button.ACTION); optionSelectUiHandler.processInput(Button.ACTION);
await new Promise<void>((resolve) => {
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => { game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.SUBMIT); handler.processInput(Button.SUBMIT);
@ -417,10 +390,8 @@ describe("UI - Starter select", () => {
game.onNextPrompt("SelectStarterPhase", Mode.SAVE_SLOT, () => { game.onNextPrompt("SelectStarterPhase", Mode.SAVE_SLOT, () => {
const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler; const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler;
saveSlotSelectUiHandler.processInput(Button.ACTION); saveSlotSelectUiHandler.processInput(Button.ACTION);
resolve();
}); });
game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.ACTION);
}); });
await game.phaseInterceptor.whenAboutToRun(EncounterPhase); await game.phaseInterceptor.whenAboutToRun(EncounterPhase);
@ -429,15 +400,19 @@ describe("UI - Starter select", () => {
expect(game.scene.getParty()[0].variant).toBe(1); expect(game.scene.getParty()[0].variant).toBe(1);
}, 20000); }, 20000);
it("Bulbasaur - shiny - variant 1", async() => { it("Bulbasaur - shiny - variant 2", async() => {
await game.importData("src/test/utils/saves/everything.prsv"); 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(); await game.runToTitle();
game.onNextPrompt("TitlePhase", Mode.TITLE, () => { game.onNextPrompt("TitlePhase", Mode.TITLE, () => {
const currentPhase = game.scene.getCurrentPhase() as TitlePhase; const currentPhase = game.scene.getCurrentPhase() as TitlePhase;
currentPhase.gameMode = GameModes.CLASSIC; currentPhase.gameMode = GameModes.CLASSIC;
currentPhase.end(); currentPhase.end();
}); });
await game.phaseInterceptor.mustRun(SelectStarterPhase).catch((error) => expect(error).toBe(SelectStarterPhase));
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => { game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.RIGHT); handler.processInput(Button.RIGHT);
@ -445,7 +420,9 @@ describe("UI - Starter select", () => {
handler.processInput(Button.V); handler.processInput(Button.V);
handler.processInput(Button.V); handler.processInput(Button.V);
handler.processInput(Button.ACTION); handler.processInput(Button.ACTION);
game.phaseInterceptor.unlock();
}); });
await game.phaseInterceptor.run(SelectStarterPhase);
let options: OptionSelectItem[]; let options: OptionSelectItem[];
let optionSelectUiHandler: OptionSelectUiHandler; let optionSelectUiHandler: OptionSelectUiHandler;
await new Promise<void>((resolve) => { await new Promise<void>((resolve) => {
@ -462,6 +439,7 @@ describe("UI - Starter select", () => {
expect(options.some(option => option.label === "Cancel")).toBe(true); expect(options.some(option => option.label === "Cancel")).toBe(true);
optionSelectUiHandler.processInput(Button.ACTION); optionSelectUiHandler.processInput(Button.ACTION);
await new Promise<void>((resolve) => {
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => { game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.SUBMIT); handler.processInput(Button.SUBMIT);
@ -473,10 +451,8 @@ describe("UI - Starter select", () => {
game.onNextPrompt("SelectStarterPhase", Mode.SAVE_SLOT, () => { game.onNextPrompt("SelectStarterPhase", Mode.SAVE_SLOT, () => {
const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler; const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler;
saveSlotSelectUiHandler.processInput(Button.ACTION); saveSlotSelectUiHandler.processInput(Button.ACTION);
resolve();
}); });
game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.ACTION);
}); });
await game.phaseInterceptor.whenAboutToRun(EncounterPhase); await game.phaseInterceptor.whenAboutToRun(EncounterPhase);
@ -485,15 +461,19 @@ describe("UI - Starter select", () => {
expect(game.scene.getParty()[0].variant).toBe(2); expect(game.scene.getParty()[0].variant).toBe(2);
}, 20000); }, 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"); 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(); await game.runToTitle();
game.onNextPrompt("TitlePhase", Mode.TITLE, () => { game.onNextPrompt("TitlePhase", Mode.TITLE, () => {
const currentPhase = game.scene.getCurrentPhase() as TitlePhase; const currentPhase = game.scene.getCurrentPhase() as TitlePhase;
currentPhase.gameMode = GameModes.CLASSIC; currentPhase.gameMode = GameModes.CLASSIC;
currentPhase.end(); currentPhase.end();
}); });
await game.phaseInterceptor.mustRun(SelectStarterPhase).catch((error) => expect(error).toBe(SelectStarterPhase));
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => { game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.RIGHT); handler.processInput(Button.RIGHT);
@ -501,7 +481,9 @@ describe("UI - Starter select", () => {
handler.processInput(Button.RIGHT); handler.processInput(Button.RIGHT);
handler.processInput(Button.RIGHT); handler.processInput(Button.RIGHT);
handler.processInput(Button.ACTION); handler.processInput(Button.ACTION);
game.phaseInterceptor.unlock();
}); });
await game.phaseInterceptor.run(SelectStarterPhase);
let options: OptionSelectItem[]; let options: OptionSelectItem[];
let optionSelectUiHandler: OptionSelectUiHandler; let optionSelectUiHandler: OptionSelectUiHandler;
await new Promise<void>((resolve) => { await new Promise<void>((resolve) => {
@ -526,6 +508,7 @@ describe("UI - Starter select", () => {
resolve(); resolve();
}); });
}); });
expect(starterSelectUiHandler.starterGens[0]).toBe(0); expect(starterSelectUiHandler.starterGens[0]).toBe(0);
expect(starterSelectUiHandler.starterCursors[0]).toBe(3); expect(starterSelectUiHandler.starterCursors[0]).toBe(3);
expect(starterSelectUiHandler.cursorObj.x).toBe(132 + 4 * 18); expect(starterSelectUiHandler.cursorObj.x).toBe(132 + 4 * 18);
@ -539,23 +522,23 @@ describe("UI - Starter select", () => {
const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler; const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler;
saveSlotSelectUiHandler.processInput(Button.ACTION); 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); await game.phaseInterceptor.whenAboutToRun(EncounterPhase);
expect(game.scene.getParty()[0].species.speciesId).toBe(Species.CATERPIE); expect(game.scene.getParty()[0].species.speciesId).toBe(Species.CATERPIE);
}, 20000); }, 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"); 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(); await game.runToTitle();
game.onNextPrompt("TitlePhase", Mode.TITLE, () => { game.onNextPrompt("TitlePhase", Mode.TITLE, () => {
const currentPhase = game.scene.getCurrentPhase() as TitlePhase; const currentPhase = game.scene.getCurrentPhase() as TitlePhase;
currentPhase.gameMode = GameModes.CLASSIC; currentPhase.gameMode = GameModes.CLASSIC;
currentPhase.end(); currentPhase.end();
}); });
await game.phaseInterceptor.mustRun(SelectStarterPhase).catch((error) => expect(error).toBe(SelectStarterPhase));
game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => { game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.RIGHT); handler.processInput(Button.RIGHT);
@ -564,7 +547,9 @@ describe("UI - Starter select", () => {
handler.processInput(Button.RIGHT); handler.processInput(Button.RIGHT);
handler.processInput(Button.DOWN); handler.processInput(Button.DOWN);
handler.processInput(Button.ACTION); handler.processInput(Button.ACTION);
game.phaseInterceptor.unlock();
}); });
await game.phaseInterceptor.run(SelectStarterPhase);
let options: OptionSelectItem[]; let options: OptionSelectItem[];
let optionSelectUiHandler: OptionSelectUiHandler; let optionSelectUiHandler: OptionSelectUiHandler;
await new Promise<void>((resolve) => { await new Promise<void>((resolve) => {
@ -589,6 +574,7 @@ describe("UI - Starter select", () => {
resolve(); resolve();
}); });
}); });
expect(starterSelectUiHandler.starterGens[0]).toBe(0); expect(starterSelectUiHandler.starterGens[0]).toBe(0);
expect(starterSelectUiHandler.starterCursors[0]).toBe(12); expect(starterSelectUiHandler.starterCursors[0]).toBe(12);
expect(starterSelectUiHandler.cursorObj.x).toBe(132 + 4 * 18); expect(starterSelectUiHandler.cursorObj.x).toBe(132 + 4 * 18);
@ -602,10 +588,6 @@ describe("UI - Starter select", () => {
const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler; const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler;
saveSlotSelectUiHandler.processInput(Button.ACTION); 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); await game.phaseInterceptor.whenAboutToRun(EncounterPhase);
expect(game.scene.getParty()[0].species.speciesId).toBe(Species.NIDORAN_M); expect(game.scene.getParty()[0].species.speciesId).toBe(Species.NIDORAN_M);
}, 20000); }, 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 {Mode} from "#app/ui/ui";
import {generateStarter, waitUntil} from "#app/test/utils/gameManagerUtils"; import {generateStarter, waitUntil} from "#app/test/utils/gameManagerUtils";
import { import {
CheckSwitchPhase,
CommandPhase, CommandPhase,
EncounterPhase, EncounterPhase,
LoginPhase, LoginPhase,
PostSummonPhase, PostSummonPhase,
SelectGenderPhase, SelectGenderPhase,
SelectStarterPhase, SelectStarterPhase,
SummonPhase,
TitlePhase, TitlePhase,
ToggleDoublePositionPhase,
} from "#app/phases"; } from "#app/phases";
import BattleScene from "#app/battle-scene.js"; import BattleScene from "#app/battle-scene.js";
import PhaseInterceptor from "#app/test/utils/phaseInterceptor"; import PhaseInterceptor from "#app/test/utils/phaseInterceptor";
import TextInterceptor from "#app/test/utils/TextInterceptor"; import TextInterceptor from "#app/test/utils/TextInterceptor";
import {expect} from "vitest";
import {GameModes, getGameMode} from "#app/game-mode"; import {GameModes, getGameMode} from "#app/game-mode";
import fs from "fs"; import fs from "fs";
import { AES, enc } from "crypto-js"; 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 {GameDataType} from "#app/data/enums/game-data-type";
import InputsHandler from "#app/test/utils/inputsHandler"; import InputsHandler from "#app/test/utils/inputsHandler";
import {ExpNotification} from "#app/enums/exp-notification"; import {ExpNotification} from "#app/enums/exp-notification";
import ErrorInterceptor from "#app/test/utils/errorInterceptor";
/** /**
* Class to manage the game state and transitions between phases. * 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. * @param bypassLogin - Whether to bypass the login phase.
*/ */
constructor(phaserGame: Phaser.Game, bypassLogin: boolean = true) { constructor(phaserGame: Phaser.Game, bypassLogin: boolean = true) {
localStorage.clear();
ErrorInterceptor.getInstance().clear();
BattleScene.prototype.randBattleSeedInt = (arg) => arg-1; BattleScene.prototype.randBattleSeedInt = (arg) => arg-1;
this.gameWrapper = new GameWrapper(phaserGame, bypassLogin); this.gameWrapper = new GameWrapper(phaserGame, bypassLogin);
this.scene = new BattleScene(); this.scene = new BattleScene();
@ -94,14 +93,14 @@ export default class GameManager {
* @returns A promise that resolves when the title phase is reached. * @returns A promise that resolves when the title phase is reached.
*/ */
runToTitle(): Promise<void> { runToTitle(): Promise<void> {
return new Promise(async(resolve) => { return new Promise(async(resolve, reject) => {
await this.phaseInterceptor.run(LoginPhase); await this.phaseInterceptor.run(LoginPhase).catch((e) => reject(e));
this.onNextPrompt("SelectGenderPhase", Mode.OPTION_SELECT, () => { this.onNextPrompt("SelectGenderPhase", Mode.OPTION_SELECT, () => {
this.scene.gameData.gender = PlayerGender.MALE; this.scene.gameData.gender = PlayerGender.MALE;
this.endPhase(); this.endPhase();
}, () => this.isCurrentPhase(TitlePhase)); }, () => this.isCurrentPhase(TitlePhase));
await this.phaseInterceptor.run(SelectGenderPhase, () => this.isCurrentPhase(TitlePhase)); await this.phaseInterceptor.run(SelectGenderPhase, () => this.isCurrentPhase(TitlePhase)).catch((e) => reject(e));
await this.phaseInterceptor.run(TitlePhase); await this.phaseInterceptor.run(TitlePhase).catch((e) => reject(e));
this.scene.gameSpeed = 5; this.scene.gameSpeed = 5;
this.scene.moveAnimations = false; this.scene.moveAnimations = false;
this.scene.showLevelUpStats = false; this.scene.showLevelUpStats = false;
@ -118,8 +117,8 @@ export default class GameManager {
* @returns A promise that resolves when the summon phase is reached. * @returns A promise that resolves when the summon phase is reached.
*/ */
runToSummon(species?: Species[]): Promise<void> { runToSummon(species?: Species[]): Promise<void> {
return new Promise(async(resolve) => { return new Promise(async(resolve, reject) => {
await this.runToTitle(); await this.runToTitle().catch((e) => reject(e));
this.onNextPrompt("TitlePhase", Mode.TITLE, () => { this.onNextPrompt("TitlePhase", Mode.TITLE, () => {
this.scene.gameMode = getGameMode(GameModes.CLASSIC); this.scene.gameMode = getGameMode(GameModes.CLASSIC);
const starters = generateStarter(this.scene, species); const starters = generateStarter(this.scene, species);
@ -127,7 +126,7 @@ export default class GameManager {
this.scene.pushPhase(new EncounterPhase(this.scene, false)); this.scene.pushPhase(new EncounterPhase(this.scene, false));
selectStarterPhase.initBattle(starters); selectStarterPhase.initBattle(starters);
}); });
await this.phaseInterceptor.run(EncounterPhase); await this.phaseInterceptor.run(EncounterPhase).catch((e) => reject(e));
resolve(); resolve();
}); });
} }
@ -138,25 +137,18 @@ export default class GameManager {
* @returns A promise that resolves when the battle is started. * @returns A promise that resolves when the battle is started.
*/ */
startBattle(species?: Species[]): Promise<void> { startBattle(species?: Species[]): Promise<void> {
return new Promise(async(resolve) => { return new Promise(async(resolve, reject) => {
await this.runToSummon(species); await this.runToSummon(species).catch((e) => reject(e));
await this.phaseInterceptor.runFrom(PostSummonPhase).to(ToggleDoublePositionPhase);
await this.phaseInterceptor.run(SummonPhase, () => this.isCurrentPhase(CheckSwitchPhase) || this.isCurrentPhase(PostSummonPhase));
this.onNextPrompt("CheckSwitchPhase", Mode.CONFIRM, () => { this.onNextPrompt("CheckSwitchPhase", Mode.CONFIRM, () => {
this.setMode(Mode.MESSAGE); this.setMode(Mode.MESSAGE);
this.endPhase(); this.endPhase();
}, () => this.isCurrentPhase(PostSummonPhase)); }, () => this.isCurrentPhase(CommandPhase));
this.onNextPrompt("CheckSwitchPhase", Mode.CONFIRM, () => { this.onNextPrompt("CheckSwitchPhase", Mode.CONFIRM, () => {
this.setMode(Mode.MESSAGE); this.setMode(Mode.MESSAGE);
this.endPhase(); this.endPhase();
}, () => this.isCurrentPhase(PostSummonPhase)); }, () => this.isCurrentPhase(CommandPhase));
await this.phaseInterceptor.run(CheckSwitchPhase, () => this.isCurrentPhase(PostSummonPhase)); await this.phaseInterceptor.runFrom(PostSummonPhase).to(CommandPhase).catch((e) => reject(e));
await this.phaseInterceptor.run(CheckSwitchPhase, () => this.isCurrentPhase(PostSummonPhase));
await this.phaseInterceptor.runFrom(PostSummonPhase).to(CommandPhase);
await waitUntil(() => this.scene.ui?.getMode() === Mode.COMMAND);
console.log("==================[New Turn]=================="); console.log("==================[New Turn]==================");
expect(this.scene.ui?.getMode()).toBe(Mode.COMMAND);
expect(this.scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name);
return resolve(); return resolve();
}); });
} }

View File

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

View File

@ -6,10 +6,12 @@ import {
LoginPhase, MessagePhase, MoveEffectPhase, MoveEndPhase, MovePhase, NewBattlePhase, NextEncounterPhase, LoginPhase, MessagePhase, MoveEffectPhase, MoveEndPhase, MovePhase, NewBattlePhase, NextEncounterPhase,
PostSummonPhase, PostSummonPhase,
SelectGenderPhase, SelectModifierPhase, SelectGenderPhase, SelectModifierPhase,
SelectStarterPhase, ShinySparklePhase, ShowAbilityPhase, StatChangePhase, SummonPhase, SelectStarterPhase, SelectTargetPhase, ShinySparklePhase, ShowAbilityPhase, StatChangePhase, SummonPhase,
TitlePhase, ToggleDoublePositionPhase, TurnEndPhase, TurnInitPhase, TurnStartPhase, VictoryPhase TitlePhase, ToggleDoublePositionPhase, TurnEndPhase, TurnInitPhase, TurnStartPhase, UnavailablePhase, VictoryPhase
} from "#app/phases"; } 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 { export default class PhaseInterceptor {
public scene; public scene;
@ -21,6 +23,9 @@ export default class PhaseInterceptor {
private intervalRun; private intervalRun;
private prompts; private prompts;
private phaseFrom; private phaseFrom;
private inProgress;
private originalSetMode;
private originalSuperEnd;
/** /**
* List of phases with their corresponding start methods. * List of phases with their corresponding start methods.
@ -56,6 +61,12 @@ export default class PhaseInterceptor {
[MoveEndPhase, this.startPhase], [MoveEndPhase, this.startPhase],
[StatChangePhase, this.startPhase], [StatChangePhase, this.startPhase],
[ShinySparklePhase, 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(); 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. * Method to set the starting phase.
* @param phaseFrom - The phase to start from. * @param phaseFrom - The phase to start from.
@ -86,20 +106,31 @@ export default class PhaseInterceptor {
* @param phaseTo - The phase to transition to. * @param phaseTo - The phase to transition to.
* @returns A promise that resolves when the transition is complete. * @returns A promise that resolves when the transition is complete.
*/ */
async to(phaseTo): Promise<void> { async to(phaseTo, runTarget: boolean = true): Promise<void> {
return new Promise(async (resolve) => { return new Promise(async (resolve, reject) => {
await this.run(this.phaseFrom); ErrorInterceptor.getInstance().add(this);
await this.run(this.phaseFrom).catch((e) => reject(e));
this.phaseFrom = null; this.phaseFrom = null;
const targetName = typeof phaseTo === "string" ? phaseTo : phaseTo.name; const targetName = typeof phaseTo === "string" ? phaseTo : phaseTo.name;
this.intervalRun = setInterval(async () => { this.intervalRun = setInterval(async() => {
const currentPhase = this.onHold?.length && this.onHold[0]; const currentPhase = this.onHold?.length && this.onHold[0];
if (currentPhase && currentPhase.name !== targetName) { if (currentPhase && currentPhase.name === targetName) {
await this.run(currentPhase.name);
} else if (currentPhase.name === targetName) {
await this.run(currentPhase.name);
clearInterval(this.intervalRun); clearInterval(this.intervalRun);
if (!runTarget) {
return resolve(); 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. * @returns A promise that resolves when the phase is run.
*/ */
run(phaseTarget, skipFn?): Promise<void> { 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; const targetName = typeof phaseTarget === "string" ? phaseTarget : phaseTarget.name;
this.scene.moveAnimations = null; // Mandatory to avoid crash this.scene.moveAnimations = null; // Mandatory to avoid crash
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
ErrorInterceptor.getInstance().add(this);
const interval = setInterval(async () => { const interval = setInterval(async () => {
const currentPhase = this.onHold?.length && this.onHold[0]; const currentPhase = this.onHold.shift();
if (currentPhase && currentPhase.name !== targetName) { if (currentPhase) {
reject(currentPhase); if (currentPhase.name !== targetName) {
} else if (currentPhase && currentPhase.name === targetName) {
clearInterval(interval); clearInterval(interval);
await this.run(phaseTarget); const skip = skipFn && skipFn(currentPhase.name);
resolve(); if (skip) {
} this.onHold.unshift(currentPhase);
}); ErrorInterceptor.getInstance().remove(this);
});
}
/**
* 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(); return resolve();
} else if (skipFn && skipFn()) { }
clearInterval(this.interval); clearInterval(interval);
return reject("Skipped phase"); return reject(`Wrong phase: this is ${currentPhase.name} and not ${targetName}`);
}
clearInterval(interval);
this.inProgress = {
name: currentPhase.name,
callback: () => {
ErrorInterceptor.getInstance().remove(this);
resolve();
},
onError: (error) => reject(error),
};
currentPhase.call();
}
});
});
}
whenAboutToRun(phaseTarget, skipFn?): 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.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. * Method to initialize phases and their corresponding methods.
*/ */
initPhases() { 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; const originalStart = phase.prototype.start;
this.phases[phase.name] = originalStart; this.phases[phase.name] = {
phase.prototype.start = () => method.call(this, phase); 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({ this.onHold.push({
name: phase.name, name: phase.name,
call: () => { 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. * Method to start the prompt handler.
*/ */
@ -271,9 +303,12 @@ export default class PhaseInterceptor {
*/ */
restoreOg() { restoreOg() {
for (const [phase] of this.PHASES) { 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.promptInterval);
clearInterval(this.interval); clearInterval(this.interval);
clearInterval(this.intervalRun);
} }
} }

View File

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