Fixed tests

This commit is contained in:
Bertie690 2025-04-21 15:27:01 -04:00
parent 102554cdb7
commit 024b413611
6 changed files with 74 additions and 67 deletions

View File

@ -4058,11 +4058,11 @@ export class PostTurnRestoreBerryAbAttr extends PostTurnAbAttr {
}
override canApplyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean {
// check if we have at least 1 recoverable berry
// check if we have at least 1 recoverable berry (at least 1 berry in berriesEaten is not capped)
const cappedBerries = new Set(
globalScene.getModifiers(BerryModifier, pokemon.isPlayer()).filter(
(bm) => bm.pokemonId === pokemon.id && bm.getCountUnderMax() < 1
).map((bm) => bm.berryType)
).map(bm => bm.berryType)
);
const hasBerryUnderCap = pokemon.battleData.berriesEaten.some(
@ -4156,7 +4156,7 @@ export class RepeatBerryNextTurnAbAttr extends PostTurnAbAttr {
/**
* Cause this {@linkcode Pokemon} to regurgitate and eat all berries
* inside its `berriesEatenLast` array.
* @param pokemon - The pokemon having the tummy ache
* @param pokemon - The {@linkcode Pokemon} having a bad tummy ache
* @param _passive - N/A
* @param _simulated - N/A
* @param _cancelled - N/A
@ -4175,19 +4175,22 @@ export class RepeatBerryNextTurnAbAttr extends PostTurnAbAttr {
const bMod = new BerryModifier(new BerryModifierType(berryType), pokemon.id, berryType, 1);
globalScene.eventTarget.dispatchEvent(new BerryUsedEvent(bMod)); // trigger message
}
// uncomment to make cheek pouch work with cud chew
// applyAbAttrs(HealFromBerryUseAbAttr, pokemon, new BooleanHolder(false));
}
/**
* @returns `true` if the pokemon ate anything this turn (we move it into `battleData`)
* @returns always `true`
*/
override canApplyPostTurn(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean {
this.showAbility = false; // don't show popup for turn end berry moving (should ideally be hidden)
return !!pokemon.turnData.berriesEaten.length;
return true;
}
/**
* Move this {@linkcode Pokemon}'s `berriesEaten` array inside `PokemonTurnData`
* into its `summonData`.
* Move this {@linkcode Pokemon}'s `berriesEaten` array from `PokemonTurnData`
* into `PokemonSummonData`.
* @param pokemon The {@linkcode Pokemon} having a nice snack
* @param _passive N/A
* @param _simulated N/A

View File

@ -406,7 +406,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
dataSource?.exp || getLevelTotalExp(this.level, species.growthRate);
this.levelExp = dataSource?.levelExp || 0;
// TODO?: Maybe instead of using such a giant if statement, maybe some optional chaining/null coaclescing would look better
if (dataSource) {
this.id = dataSource.id;
this.hp = dataSource.hp;
@ -454,8 +453,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
this.customPokemonData = new CustomPokemonData(
dataSource.customPokemonData,
);
this.summonData = dataSource.summonData;
this.battleData = dataSource.battleData;
this.teraType = dataSource.teraType;
this.isTerastallized = dataSource.isTerastallized;
this.stellarTypesBoosted = dataSource.stellarTypesBoosted ?? [];
@ -524,6 +521,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
this.stellarTypesBoosted = [];
}
this.summonData = new PokemonSummonData(dataSource?.summonData);
this.battleData = new PokemonBattleData(dataSource?.battleData);
this.generateName();
if (!species.isObtainable()) {

View File

@ -1,13 +1,14 @@
import { RepeatBerryNextTurnAbAttr } from "#app/data/abilities/ability";
import { getBerryEffectFunc } from "#app/data/berry";
import Pokemon from "#app/field/pokemon";
import { globalScene } from "#app/global-scene";
import { getPokemonNameWithAffix } from "#app/messages";
import { Abilities } from "#enums/abilities";
import { BerryType } from "#enums/berry-type";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import { Stat } from "#enums/stat";
import GameManager from "#test/testUtils/gameManager";
import i18next from "i18next";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
@ -23,7 +24,6 @@ describe("Abilities - Cud Chew", () => {
afterEach(() => {
game.phaseInterceptor.restoreOg();
vi.resetAllMocks();
});
beforeEach(() => {
@ -40,7 +40,7 @@ describe("Abilities - Cud Chew", () => {
});
describe("tracks berries eaten", () => {
it("stores inside battledata at end of turn", async () => {
it("stores inside summonData at end of turn", async () => {
await game.classicMode.startBattle([Species.FARIGIRAF]);
const farigiraf = game.scene.getPlayerPokemon()!;
@ -66,32 +66,44 @@ describe("Abilities - Cud Chew", () => {
expect(farigiraf.turnData.berriesEaten).toEqual([]);
});
it("shouldn't show ability popup for end-of-turn storage", async () => {
it("shows ability popup for eating berry, even if berry is useless", async () => {
const abDisplaySpy = vi.spyOn(globalScene, "queueAbilityDisplay");
game.override.enemyMoveset([Moves.SPLASH, Moves.HEAL_PULSE]);
await game.classicMode.startBattle([Species.FARIGIRAF]);
const farigiraf = game.scene.getPlayerPokemon()!;
farigiraf.hp = 1; // needed to allow sitrus procs
// Dip below half to eat berry
farigiraf.hp = farigiraf.getMaxHp() / 2 - 1;
game.move.select(Moves.SPLASH);
await game.forceEnemyMove(Moves.SPLASH);
await game.phaseInterceptor.to("TurnEndPhase");
// doesn't trigger since cud chew hasn't eaten berry yet
expect(farigiraf.summonData.berriesEatenLast).toContain(BerryType.SITRUS);
expect(abDisplaySpy).not.toHaveBeenCalledWith(farigiraf);
await game.toNextTurn();
// get heal pulsed back to full before the cud chew proc
game.move.select(Moves.SPLASH);
await game.phaseInterceptor.to("BerryPhase");
await game.forceEnemyMove(Moves.HEAL_PULSE);
await game.phaseInterceptor.to("TurnEndPhase");
// globalScene.queueAbilityDisplay should be called twice: once to show cud chew before regurgitating berries,
// once to hide after finishing application
// globalScene.queueAbilityDisplay should be called twice:
// once to show cud chew text before regurgitating berries,
// once to hide ability text after finishing.
expect(abDisplaySpy).toBeCalledTimes(2);
expect(abDisplaySpy.mock.calls[0][0]).toBe(farigiraf);
expect(abDisplaySpy.mock.calls[0][2]).toBe(true);
expect(abDisplaySpy.mock.calls[1][0]).toBe(farigiraf);
expect(abDisplaySpy.mock.calls[1][2]).toBe(false);
await game.phaseInterceptor.to("TurnEndPhase");
// should display messgae
expect(game.textInterceptor.getLatestMessage()).toBe(
i18next.t("battle:hpIsFull", {
pokemonName: getPokemonNameWithAffix(farigiraf),
}),
);
// not called again at turn end
expect(abDisplaySpy).toBeCalledTimes(2);
@ -114,7 +126,7 @@ describe("Abilities - Cud Chew", () => {
game.move.select(Moves.STUFF_CHEEKS);
await game.toNextTurn();
// Ate 2 petayas from moves + 1 of each at turn end
// Ate 2 petayas from moves + 1 of each at turn end; all 4 get moved on turn end
expect(farigiraf.summonData.berriesEatenLast).toEqual([
BerryType.PETAYA,
BerryType.PETAYA,
@ -123,23 +135,14 @@ describe("Abilities - Cud Chew", () => {
]);
expect(farigiraf.turnData.berriesEaten).toEqual([]);
game.move.select(Moves.STUFF_CHEEKS);
game.move.select(Moves.SPLASH);
await game.toNextTurn();
// previous berries moved into summon data; newly eaten berries move into turn data
expect(farigiraf.summonData.berriesEatenLast).toEqual([
BerryType.PETAYA,
BerryType.PETAYA,
BerryType.PETAYA,
BerryType.LIECHI,
]);
expect(farigiraf.turnData.berriesEaten).toEqual([BerryType.PETAYA, BerryType.LIECHI, BerryType.LIECHI]);
expect(farigiraf.getStatStage(Stat.ATK)).toBe(4); // 1 --> 2+1
await game.toNextTurn();
// 1st array overridden after turn end
expect(farigiraf.summonData.berriesEatenLast).toEqual([BerryType.PETAYA, BerryType.LIECHI, BerryType.LIECHI]);
// previous berries eaten and deleted from summon data as remaining eaten berries move to replace them
expect(farigiraf.summonData.berriesEatenLast).toEqual([BerryType.LIECHI, BerryType.LIECHI]);
expect(farigiraf.turnData.berriesEaten).toEqual([]);
expect(farigiraf.getStatStage(Stat.SPATK)).toBe(6); // 3+0+3
expect(farigiraf.getStatStage(Stat.ATK)).toBe(4); // 1+2+1
});
it("resets array on switch", async () => {
@ -246,24 +249,26 @@ describe("Abilities - Cud Chew", () => {
expect(farigiraf.hp).toBeLessThan(farigiraf.getMaxHp() / 4);
});
it("works with pluck even if berry is useless", async () => {
const bSpy = vi.fn(getBerryEffectFunc);
it("works with pluck", async () => {
game.override
.enemySpecies(Species.BLAZIKEN)
.enemyHeldItems([{ name: "BERRY", type: BerryType.SITRUS, count: 1 }])
.enemyHeldItems([{ name: "BERRY", type: BerryType.PETAYA, count: 1 }])
.startingHeldItems([]);
await game.classicMode.startBattle([Species.FARIGIRAF]);
game.move.select(Moves.BUG_BITE);
await game.toNextTurn();
const farigiraf = game.scene.getPlayerPokemon()!;
game.move.select(Moves.BUG_BITE);
await game.toNextTurn();
expect(bSpy).toBeCalledTimes(2);
game.move.select(Moves.SPLASH);
await game.toNextTurn();
// berry effect triggered twice - once for bug bite, once for cud chew
expect(farigiraf.getStatStage(Stat.SPATK)).toBe(2);
});
it("works with Ripen", async () => {
const bSpy = vi.fn(getBerryEffectFunc);
game.override.passiveAbility(Abilities.RIPEN);
await game.classicMode.startBattle([Species.FARIGIRAF]);
@ -277,7 +282,6 @@ describe("Abilities - Cud Chew", () => {
// Rounding errors only ever cost a maximum of 4 hp
expect(farigiraf.getInverseHp()).toBeLessThanOrEqual(3);
expect(bSpy).toHaveBeenCalledTimes(2);
});
it("is preserved on reload/wave clear", async () => {

View File

@ -36,7 +36,6 @@ describe("Abilities - Harvest", () => {
afterEach(() => {
game.phaseInterceptor.restoreOg();
vi.resetAllMocks();
});
beforeEach(() => {
@ -146,15 +145,26 @@ describe("Abilities - Harvest", () => {
expect(regielekiReloaded.battleData.berriesEaten).toEqual([BerryType.PETAYA]);
});
it("cannot restore capped berries", async () => {
it("cannot restore capped berries, even if an ally has one under cap", async () => {
const initBerries: ModifierOverride[] = [
{ name: "BERRY", type: BerryType.LUM, count: 2 },
{ name: "BERRY", type: BerryType.STARF, count: 2 },
];
game.override.startingHeldItems(initBerries);
await game.classicMode.startBattle([Species.FEEBAS]);
const player = game.scene.getPlayerPokemon()!;
player.battleData.berriesEaten = [BerryType.LUM, BerryType.STARF];
await game.classicMode.startBattle([Species.FEEBAS, Species.BELLOSSOM]);
const [feebas, bellossom] = game.scene.getPlayerParty();
feebas.battleData.berriesEaten = [BerryType.LUM, BerryType.STARF];
// get rid of bellossom's modifiers and add a sitrus
await game.scene.removePartyMemberModifiers(1);
const newMod = game.scene
.getModifiers(BerryModifier, true)
.find(b => b.berryType === BerryType.SITRUS)
?.clone()!;
expect(newMod).toBeDefined();
newMod.pokemonId = bellossom.id;
game.scene.addModifier(newMod, true);
game.move.select(Moves.SPLASH);
await game.forceEnemyMove(Moves.SPLASH);
@ -166,7 +176,9 @@ describe("Abilities - Harvest", () => {
vi.spyOn(Phaser.Math.RND, "integerInRange").mockReturnValue(0);
await game.phaseInterceptor.to("TurnEndPhase");
// recovered a starf,
expectBerriesContaining({ name: "BERRY", type: BerryType.STARF, count: 3 });
expect(game.scene.getModifiers(BerryModifier, true).filter(b => b.pokemonId === bellossom.id)).toHaveLength(0);
});
it("does nothing if all berries are capped", async () => {

View File

@ -4,7 +4,7 @@ import GameManager from "#test/testUtils/gameManager";
import { Species } from "#enums/species";
import { TurnEndPhase } from "#app/phases/turn-end-phase";
import { Moves } from "#enums/moves";
import { Stat, BATTLE_STATS, EFFECTIVE_STATS } from "#enums/stat";
import { Stat, EFFECTIVE_STATS } from "#enums/stat";
import { Abilities } from "#enums/abilities";
import { BattlerIndex } from "#app/battle";
@ -49,30 +49,18 @@ describe("Moves - Transform", () => {
expect(player.getAbility()).toBe(enemy.getAbility());
expect(player.getGender()).toBe(enemy.getGender());
// copies all stats except hp
expect(player.getStat(Stat.HP, false)).not.toBe(enemy.getStat(Stat.HP));
for (const s of EFFECTIVE_STATS) {
expect(player.getStat(s, false)).toBe(enemy.getStat(s, false));
}
for (const s of BATTLE_STATS) {
expect(player.getStatStage(s)).toBe(enemy.getStatStage(s));
}
expect(player.getStatStages()).toEqual(enemy.getStatStages());
const playerMoveset = player.getMoveset();
const enemyMoveset = enemy.getMoveset();
// move IDs are equal
expect(player.getMoveset().map(m => m.moveId)).toEqual(enemy.getMoveset().map(m => m.moveId));
expect(playerMoveset.length).toBe(enemyMoveset.length);
for (let i = 0; i < playerMoveset.length && i < enemyMoveset.length; i++) {
expect(playerMoveset[i]?.moveId).toBe(enemyMoveset[i]?.moveId);
}
const playerTypes = player.getTypes();
const enemyTypes = enemy.getTypes();
expect(playerTypes.length).toBe(enemyTypes.length);
for (let i = 0; i < playerTypes.length && i < enemyTypes.length; i++) {
expect(playerTypes[i]).toBe(enemyTypes[i]);
}
expect(player.getTypes()).toEqual(enemy.getTypes());
});
it("should copy in-battle overridden stats", async () => {

View File

@ -73,6 +73,6 @@ export class ReloadHelper extends GameManagerHelper {
}
await this.game.phaseInterceptor.to(CommandPhase);
console.log("==================[New Turn]==================");
console.log("==================[New Turn (Reloaded)]==================");
}
}