Added toHaveUsedPP matcher

This commit is contained in:
Bertie690 2025-07-27 22:16:52 -04:00
parent 39230e1864
commit b384535188
4 changed files with 95 additions and 15 deletions

View File

@ -13,6 +13,7 @@ import type { toHaveTypesOptions } from "#test/test-utils/matchers/to-have-types
import { TurnMove } from "#types/turn-move";
import { AtLeastOne } from "#types/type-helpers";
import type { expect } from "vitest";
import type Overrides from "#app/overrides";
declare module "vitest" {
interface Assertion {
@ -20,7 +21,7 @@ declare module "vitest" {
* Matcher to check if an array contains EXACTLY the given items (in any order).
*
* Different from {@linkcode expect.arrayContaining} as the latter only checks for subset equality
* (as opposed to full equality)
* (as opposed to full equality).
*
* @param expected - The expected contents of the array, in any order
* @see {@linkcode expect.arrayContaining}
@ -28,10 +29,10 @@ declare module "vitest" {
toEqualArrayUnsorted<E>(expected: E[]): void;
/**
* Matcher to check if a {@linkcode Pokemon}'s current typing includes the given types.
* Matcher to check if a {@linkcode Pokemon}'s current typing includes the given types
*
* @param expected - The expected types (in any order).
* @param options - The options passed to the matcher.
* @param expected - The expected types (in any order)
* @param options - The options passed to the matcher
*/
toHaveTypes(expected: [PokemonType, ...PokemonType[]], options?: toHaveTypesOptions): void;
@ -47,7 +48,7 @@ declare module "vitest" {
/**
* Matcher to check if a {@linkcode Pokemon Pokemon's} effective stat is as expected
* (checked after all mstat value modifications).
* (checked after all stat value modifications).
*
* @param stat - The {@linkcode EffectiveStat} to check
* @param expectedValue - The expected value of {@linkcode stat}
@ -64,13 +65,6 @@ declare module "vitest" {
*/
toHaveTakenDamage(expectedDamageTaken: number, roundDown?: boolean): void;
/**
* Matcher to check if a {@linkcode Pokemon} has a specific {@linkcode StatusEffect | non-volatile status effect}.
* @param expectedStatusEffect - The {@linkcode StatusEffect} the Pokemon is expected to have,
* or a partially filled {@linkcode Status} containing the desired properties
*/
toHaveStatusEffect(expectedStatusEffect: expectedStatusType): void;
/**
* Matcher to check if the current {@linkcode WeatherType} is as expected.
* @param expectedWeatherType - The expected {@linkcode WeatherType}
@ -84,10 +78,17 @@ declare module "vitest" {
toHaveTerrain(expectedTerrainType: TerrainType): void;
/**
* Matcher to check if a {@linkcode Pokemon} has full HP.
* Matcher to check if a {@linkcode Pokemon} is at full HP.
*/
toHaveFullHp(): void;
/**
* Matcher to check if a {@linkcode Pokemon} has a specific {@linkcode StatusEffect | non-volatile status effect}.
* @param expectedStatusEffect - The {@linkcode StatusEffect} the Pokemon is expected to have,
* or a partially filled {@linkcode Status} containing the desired properties
*/
toHaveStatusEffect(expectedStatusEffect: expectedStatusType): void;
/**
* Matcher to check if a {@linkcode Pokemon} has a specific {@linkcode Stat} stage.
* @param stat - The {@linkcode BattleStat} to check
@ -102,7 +103,7 @@ declare module "vitest" {
toHaveBattlerTag(expectedBattlerTagType: BattlerTagType): void;
/**
* Matcher to check if a {@linkcode Pokemon} had a specific {@linkcode AbilityId} applied.
* Matcher to check if a {@linkcode Pokemon} has applied a specific {@linkcode AbilityId}.
* @param expectedAbilityId - The expected {@linkcode AbilityId}
*/
toHaveAbilityApplied(expectedAbilityId: AbilityId): void;
@ -117,5 +118,15 @@ declare module "vitest" {
* Matcher to check if a {@linkcode Pokemon} has fainted (as determined by {@linkcode Pokemon.isFainted}).
*/
toHaveFainted(): void;
/**
* Matcher to check th
* @param expectedValue - The {@linkcode MoveId} that should have consumed PP
* @param ppUsed - The amount of PP that should have been consumed
* @remarks
* If the Pokemon's moveset has been set via {@linkcode Overrides.MOVESET_OVERRIDE} or {@linkcode OPP_MOVESET_OVERRIDE},
* or contains the desired move more than once, this will fail the test.
*/
toHaveUsedPP(expectedMove: MoveId, ppUsed: number): void;
}
}

View File

@ -11,6 +11,7 @@ import { toHaveTakenDamage } from "#test/test-utils/matchers/to-have-taken-damag
import { toHaveTerrain } from "#test/test-utils/matchers/to-have-terrain";
import { toHaveTypes } from "#test/test-utils/matchers/to-have-types";
import { toHaveUsedMove } from "#test/test-utils/matchers/to-have-used-move";
import { toHaveUsedPP } from "#test/test-utils/matchers/to-have-used-pp";
import { toHaveWeather } from "#test/test-utils/matchers/to-have-weather";
import { expect } from "vitest";
@ -34,4 +35,5 @@ expect.extend({
toHaveAbilityApplied,
toHaveHp,
toHaveFainted,
toHaveUsedPP,
});

View File

@ -42,6 +42,6 @@ describe("Moves - Spite", () => {
await game.toEndOfTurn();
const karp = game.field.getEnemyPokemon();
expect(karp.getMoveset()).toBe(true);
expect(karp.getMoveset()).toBe();
});
});

View File

@ -0,0 +1,67 @@
import { getPokemonNameWithAffix } from "#app/messages";
import Overrides from "#app/overrides";
import { MoveId } from "#enums/move-id";
// biome-ignore lint/correctness/noUnusedImports: TSDocs
import type { Pokemon } from "#field/pokemon";
import { getEnumStr } from "#test/test-utils/string-utils";
import { isPokemonInstance, receivedStr } from "#test/test-utils/test-utils";
import { coerceArray } from "#utils/common";
import type { MatcherState, SyncExpectationResult } from "@vitest/expect";
/**
* Matcher to check the amount of PP consumed by a {@linkcode Pokemon}.
* @param received - The actual value received. Should be a {@linkcode Pokemon}
* @param expectedValue - The {@linkcode MoveId} that should have consumed PP
* @param ppUsed - The amount of PP that should have been consumed
* @returns Whether the matcher passed
* @remarks
* If the same move appears in the Pokemon's moveset multiple times, this will fail the test!
*/
export function toHaveUsedPP(
this: MatcherState,
received: unknown,
expectedMove: MoveId,
ppUsed: number,
): SyncExpectationResult {
if (!isPokemonInstance(received)) {
return {
pass: false,
message: () => `Expected to receive a Pokémon, but got ${receivedStr(received)}!`,
};
}
const override = received.isPlayer() ? Overrides.MOVESET_OVERRIDE : Overrides.OPP_MOVESET_OVERRIDE;
if (coerceArray(override).length > 0) {
return {
pass: false,
message: () =>
`Cannot test for PP consumption with ${received.isPlayer() ? "player" : "enemy"} moveset overrides active!`,
};
}
const pkmName = getPokemonNameWithAffix(received);
const moveStr = getEnumStr(MoveId, expectedMove);
const movesetMoves = received.getMoveset().filter(pm => pm.moveId === expectedMove);
if (movesetMoves.length !== 1) {
return {
pass: false,
message: () =>
`Expected MoveId.${moveStr} to appear in ${pkmName}'s moveset exactly once, but got ${movesetMoves.length} times!`,
actual: received.getMoveset(),
};
}
const move = movesetMoves[0];
const pass = move.ppUsed === ppUsed;
return {
pass,
message: () =>
pass
? `Expected ${pkmName}'s ${moveStr} to NOT have used ${ppUsed} PP, but it did!`
: `Expected ${pkmName}'s ${moveStr} to have used ${ppUsed} PP, but got ${move.ppUsed} instead!`,
expected: ppUsed,
actual: move.ppUsed,
};
}