[Dev] Move function from testUtils.ts to gameManager.ts (1/3) (#3430)

* move mockHitCheck to gameManager

* add abstract base class and move helper class

* add param for single target miss
This commit is contained in:
Adrian T. 2024-08-09 22:37:10 +08:00 committed by GitHub
parent 14b3907b11
commit 2b12326280
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 77 additions and 52 deletions

View File

@ -8,7 +8,7 @@ import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { mockHitCheck, SPLASH_ONLY } from "#test/utils/testUtils";
import { SPLASH_ONLY } from "#test/utils/testUtils";
describe("Abilities - Hustle", () => {
let phaserGame: Phaser.Game;
@ -44,7 +44,7 @@ describe("Abilities - Hustle", () => {
vi.spyOn(pikachu, "getBattleStat");
game.doAttack(getMovePosition(game.scene, 0, Moves.TACKLE));
await mockHitCheck(game, true);
await game.move.forceHit();
await game.phaseInterceptor.to(DamagePhase);
expect(pikachu.getBattleStat).toHaveReturnedWith(atk * 1.5);

View File

@ -12,7 +12,7 @@ import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from "vitest";
import GameManager from "#test/utils/gameManager";
import { getMovePosition } from "#test/utils/gameManagerUtils";
import { mockHitCheck, SPLASH_ONLY } from "#test/utils/testUtils";
import { SPLASH_ONLY } from "#test/utils/testUtils";
const TIMEOUT = 20 * 1000;
@ -192,7 +192,7 @@ describe("Abilities - Protean", () => {
expect(leadPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.TACKLE));
await mockHitCheck(game, false);
await game.move.forceMiss();
await game.phaseInterceptor.to(TurnEndPhase);
const enemyPokemon = game.scene.getEnemyPokemon()!;

View File

@ -11,7 +11,7 @@ import { Abilities } from "#enums/abilities";
import { WeatherType } from "#app/data/weather.js";
import { StatusEffect, getStatusEffectCatchRateMultiplier } from "#app/data/status-effect";
import { BattlerTagType } from "#enums/battler-tag-type";
import { mockHitCheck, SPLASH_ONLY } from "#test/utils/testUtils";
import { SPLASH_ONLY } from "#test/utils/testUtils";
const TIMEOUT = 20 * 1000; // 20 sec timeout
@ -258,7 +258,7 @@ describe("Abilities - Magic Guard", () => {
const leadPokemon = game.scene.getPlayerPokemon()!;
game.doAttack(getMovePosition(game.scene, 0, Moves.HIGH_JUMP_KICK));
await mockHitCheck(game, false);
await game.move.forceMiss();
await game.phaseInterceptor.to(TurnEndPhase);

View File

@ -10,7 +10,7 @@ import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest";
import GameManager from "#test/utils/gameManager";
import { getMovePosition } from "#test/utils/gameManagerUtils";
import { mockHitCheck, SPLASH_ONLY } from "#test/utils/testUtils";
import { SPLASH_ONLY } from "#test/utils/testUtils";
const TIMEOUT = 20 * 1000;
@ -129,7 +129,7 @@ describe("Abilities - Parental Bond", () => {
expect(enemyPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.DOUBLE_HIT));
await mockHitCheck(game, true);
await game.move.forceHit();
await game.phaseInterceptor.to(BerryPhase, false);
@ -172,7 +172,7 @@ describe("Abilities - Parental Bond", () => {
expect(enemyPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.ROLLOUT));
await mockHitCheck(game, true);
await game.move.forceHit();
await game.phaseInterceptor.to(DamagePhase, false);
@ -368,7 +368,7 @@ describe("Abilities - Parental Bond", () => {
const enemyStartingHp = enemyPokemon.hp;
game.doAttack(getMovePosition(game.scene, 0, Moves.SUPER_FANG));
await mockHitCheck(game, true);
await game.move.forceHit();
await game.phaseInterceptor.to(DamagePhase);
@ -397,7 +397,7 @@ describe("Abilities - Parental Bond", () => {
const enemyStartingHp = enemyPokemon.hp;
game.doAttack(getMovePosition(game.scene, 0, Moves.SEISMIC_TOSS));
await mockHitCheck(game, true);
await game.move.forceHit();
await game.phaseInterceptor.to(DamagePhase);
@ -423,7 +423,7 @@ describe("Abilities - Parental Bond", () => {
expect(enemyPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.HYPER_BEAM));
await mockHitCheck(game, true);
await game.move.forceHit();
await game.phaseInterceptor.to(DamagePhase);
@ -451,7 +451,7 @@ describe("Abilities - Parental Bond", () => {
expect(enemyPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.ANCHOR_SHOT));
await mockHitCheck(game, true);
await game.move.forceHit();
await game.phaseInterceptor.to(DamagePhase);
@ -481,7 +481,7 @@ describe("Abilities - Parental Bond", () => {
expect(enemyPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.SMACK_DOWN));
await mockHitCheck(game, true);
await game.move.forceHit();
await game.phaseInterceptor.to(DamagePhase);
@ -508,7 +508,7 @@ describe("Abilities - Parental Bond", () => {
expect(enemyPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.U_TURN));
await mockHitCheck(game, true);
await game.move.forceHit();
await game.phaseInterceptor.to(MoveEffectPhase);
expect(leadPokemon.turnData.hitCount).toBe(2);
@ -532,7 +532,7 @@ describe("Abilities - Parental Bond", () => {
expect(enemyPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.WAKE_UP_SLAP));
await mockHitCheck(game, true);
await game.move.forceHit();
await game.phaseInterceptor.to(DamagePhase);

View File

@ -12,7 +12,7 @@ import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from "vitest";
import GameManager from "#test/utils/gameManager";
import { getMovePosition } from "#test/utils/gameManagerUtils";
import { mockHitCheck, SPLASH_ONLY } from "#test/utils/testUtils";
import { SPLASH_ONLY } from "#test/utils/testUtils";
const TIMEOUT = 20 * 1000;
@ -192,7 +192,7 @@ describe("Abilities - Protean", () => {
expect(leadPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.TACKLE));
await mockHitCheck(game, false);
await game.move.forceMiss();
await game.phaseInterceptor.to(TurnEndPhase);
const enemyPokemon = game.scene.getEnemyPokemon()!;

View File

@ -8,7 +8,7 @@ import { getMovePosition } from "#test/utils/gameManagerUtils";
import { BattlerTagType } from "#app/enums/battler-tag-type.js";
import { Abilities } from "#app/enums/abilities.js";
import { BattlerIndex } from "#app/battle.js";
import { mockHitCheck, SPLASH_ONLY } from "#test/utils/testUtils";
import { SPLASH_ONLY } from "#test/utils/testUtils";
describe("Abilities - Sweet Veil", () => {
let phaserGame: Phaser.Game;
@ -80,11 +80,11 @@ describe("Abilities - Sweet Veil", () => {
game.doAttack(getMovePosition(game.scene, 1, Moves.SPLASH));
// First pokemon move
await mockHitCheck(game, true);
await game.move.forceHit();
// Second pokemon move
await game.phaseInterceptor.to(MovePhase, false);
await mockHitCheck(game, true);
await game.move.forceHit();
expect(game.scene.getPlayerField().some(p => !!p.getTag(BattlerTagType.DROWSY))).toBe(true);

View File

@ -1,12 +1,12 @@
import { BattleStat } from "#app/data/battle-stat.js";
import { MoveEffectPhase, MoveEndPhase, StatChangePhase } from "#app/phases";
import { MoveEndPhase, StatChangePhase } from "#app/phases";
import GameManager from "#test/utils/gameManager";
import { getMovePosition } from "#test/utils/gameManagerUtils";
import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
import { SPLASH_ONLY } from "#test/utils/testUtils";
const TIMEOUT = 20 * 1000;
@ -91,11 +91,8 @@ describe("Moves - Make It Rain", () => {
game.doAttack(getMovePosition(game.scene, 0, Moves.MAKE_IT_RAIN));
game.doAttack(getMovePosition(game.scene, 1, Moves.SPLASH));
await game.phaseInterceptor.to(MoveEffectPhase, false);
// Make Make It Rain miss the first target
const moveEffectPhase = game.scene.getCurrentPhase() as MoveEffectPhase;
vi.spyOn(moveEffectPhase, "hitCheck").mockReturnValueOnce(false);
await game.move.forceMiss(true);
await game.phaseInterceptor.to(MoveEndPhase);

View File

@ -28,6 +28,7 @@ import { ModifierTypeOption, modifierTypes } from "#app/modifier/modifier-type.j
import overrides from "#app/overrides.js";
import { removeEnemyHeldItems } from "./testUtils";
import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler.js";
import { MoveHelper } from "./moveHelper";
/**
* Class to manage the game state and transitions between phases.
@ -39,6 +40,7 @@ export default class GameManager {
public textInterceptor: TextInterceptor;
public inputsHandler: InputsHandler;
public readonly override: OverridesHelper;
public readonly move: MoveHelper;
/**
* Creates an instance of GameManager.
@ -55,6 +57,7 @@ export default class GameManager {
this.textInterceptor = new TextInterceptor(this.scene);
this.gameWrapper.setScene(this.scene);
this.override = new OverridesHelper(this);
this.move = new MoveHelper(this);
}
/**

View File

@ -0,0 +1,12 @@
import GameManager from "./gameManager";
/**
* Base class for defining all game helpers.
*/
export abstract class GameManagerHelper {
protected readonly game: GameManager;
constructor(game: GameManager) {
this.game = game;
}
}

View File

@ -0,0 +1,35 @@
import { vi } from "vitest";
import { MoveEffectPhase } from "#app/phases.js";
import { GameManagerHelper } from "./gameManagerHelper";
/**
* Helper to handle a Pokemon's move
*/
export class MoveHelper extends GameManagerHelper {
/**
* Intercepts `MoveEffectPhase` and mocks the hitCheck's
* return value to `true` {@linkcode MoveEffectPhase.hitCheck}.
* Used to force a move to hit.
*/
async forceHit(): Promise<void> {
await this.game.phaseInterceptor.to(MoveEffectPhase, false);
vi.spyOn(this.game.scene.getCurrentPhase() as MoveEffectPhase, "hitCheck").mockReturnValue(true);
}
/**
* Intercepts `MoveEffectPhase` and mocks the hitCheck's
* return value to `false` {@linkcode MoveEffectPhase.hitCheck}.
* Used to force a move to miss.
* @param firstTargetOnly Whether the move should force miss on the first target only, in the case of multi-hit moves.
*/
async forceMiss(firstTargetOnly: boolean = false): Promise<void> {
await this.game.phaseInterceptor.to(MoveEffectPhase, false);
const hitCheck = vi.spyOn(this.game.scene.getCurrentPhase() as MoveEffectPhase, "hitCheck");
if (firstTargetOnly) {
hitCheck.mockReturnValueOnce(false);
} else {
hitCheck.mockReturnValue(false);
}
}
}

View File

@ -8,19 +8,13 @@ import * as GameMode from "#app/game-mode";
import { GameModes, getGameMode } from "#app/game-mode";
import { ModifierOverride } from "#app/modifier/modifier-type.js";
import Overrides from "#app/overrides";
import GameManager from "#test/utils/gameManager";
import { vi } from "vitest";
import { GameManagerHelper } from "./gameManagerHelper";
/**
* Helper to handle overrides in tests
*/
export class OverridesHelper {
private readonly game: GameManager;
constructor(game: GameManager) {
this.game = game;
}
export class OverridesHelper extends GameManagerHelper {
/**
* Override the starting biome
* @warning Any event listeners that are attached to [NewArenaEvent](events\battle-scene.ts) may need to be handled down the line

View File

@ -3,7 +3,7 @@ import i18next, { type ParseKeys } from "i18next";
import { vi } from "vitest";
import GameManager from "./gameManager";
import { BattlerIndex } from "#app/battle.js";
import { MoveEffectPhase, TurnStartPhase } from "#app/phases.js";
import { TurnStartPhase } from "#app/phases.js";
/** Ready to use array of Moves.SPLASH x4 */
export const SPLASH_ONLY = [Moves.SPLASH, Moves.SPLASH, Moves.SPLASH, Moves.SPLASH];
@ -54,19 +54,3 @@ export async function mockTurnOrder(game: GameManager, order: BattlerIndex[]): P
vi.spyOn(game.scene.getCurrentPhase() as TurnStartPhase, "getOrder").mockReturnValue(order);
}
/**
* Intercepts `MoveEffectPhase` and mocks the hitCheck's return value {@linkcode MoveEffectPhase.hitCheck}.
* Used to force a move to either hit or miss.
* Note that this uses `mockReturnValue()`, meaning it will also apply to a
* succeeding `MoveEffectPhase` immediately following the first one
* (in the case of a multi-target move)
*
* @param {GameManager} game The GameManager instance
* @param shouldHit Whether the move should hit
*/
export async function mockHitCheck(game: GameManager, shouldHit: boolean): Promise<void> {
await game.phaseInterceptor.to(MoveEffectPhase, false);
vi.spyOn(game.scene.getCurrentPhase() as MoveEffectPhase, "hitCheck").mockReturnValue(shouldHit);
}