Grabbed matchers from other branch

This commit is contained in:
Bertie690 2025-08-03 13:48:26 -04:00
parent 0bc78cb715
commit ba48f16500
4 changed files with 110 additions and 4 deletions

View File

@ -1,20 +1,27 @@
import type { TerrainType } from "#app/data/terrain";
import type { ArenaTag, ArenaTagTypeMap } from "#data/arena-tag";
import type { AbilityId } from "#enums/ability-id";
import type { ArenaTagType } from "#enums/arena-tag-type";
import type { BattlerTagType } from "#enums/battler-tag-type";
import type { MoveId } from "#enums/move-id";
import type { PokemonType } from "#enums/pokemon-type";
import type { BattleStat, EffectiveStat, Stat } from "#enums/stat";
import type { StatusEffect } from "#enums/status-effect";
import type { WeatherType } from "#enums/weather-type";
import type { Arena } from "#field/arena";
import type { Pokemon } from "#field/pokemon";
import type { ToHaveEffectiveStatMatcherOptions } from "#test/test-utils/matchers/to-have-effective-stat";
import type { expectedStatusType } from "#test/test-utils/matchers/to-have-status-effect";
import type { toHaveTypesOptions } from "#test/test-utils/matchers/to-have-types";
import type { TurnMove } from "#types/turn-move";
import type { AtLeastOne } from "#types/type-helpers";
import type { toDmgValue } from "utils/common";
import type { expect } from "vitest";
import "vitest";
import type Overrides from "#app/overrides";
import type { ArenaTagSide } from "#enums/arena-tag-side";
import type { PokemonMove } from "#moves/pokemon-move";
import type { OneOther } from "#test/@types/test-helpers";
declare module "vitest" {
interface Assertion {
@ -35,6 +42,7 @@ declare module "vitest" {
* @param expected - The expected types (in any order)
* @param options - The options passed to the matcher
*/
toHaveTypes(expected: PokemonType[], options?: toHaveTypesOptions): void;
toHaveTypes(expected: [PokemonType, ...PokemonType[]], options?: toHaveTypesOptions): void;
/**
@ -79,6 +87,24 @@ declare module "vitest" {
*/
toHaveTerrain(expectedTerrainType: TerrainType): void;
/**
* Check whether the current {@linkcode Arena} contains the given {@linkcode ArenaTag}.
*
* @param expectedType - A partially-filled {@linkcode ArenaTag} containing the desired properties
*/
toHaveArenaTag<T extends ArenaTagType>(
expectedType: OneOther<ArenaTagTypeMap[T], "tagType" | "side"> & { tagType: T }, // intersection required bc this doesn't preserve T
): void;
/**
* Check whether the current {@linkcode Arena} contains the given {@linkcode ArenaTag}.
*
* @param expectedType - The {@linkcode ArenaTagType} of the desired tag
* @param side - The {@linkcode ArenaTagSide | side of the field} the tag should affect, or
* {@linkcode ArenaTagSide.BOTH} to check both sides;
* default `ArenaTagSide.BOTH`
*/
toHaveArenaTag(expectedType: ArenaTagType, side?: ArenaTagSide): void;
/**
* Check whether a {@linkcode Pokemon} is at full HP.
*/

View File

@ -1,5 +1,6 @@
import { toEqualArrayUnsorted } from "#test/test-utils/matchers/to-equal-array-unsorted";
import { toHaveAbilityApplied } from "#test/test-utils/matchers/to-have-ability-applied";
import { toHaveArenaTag } from "#test/test-utils/matchers/to-have-arena-tag";
import { toHaveBattlerTag } from "#test/test-utils/matchers/to-have-battler-tag";
import { toHaveEffectiveStat } from "#test/test-utils/matchers/to-have-effective-stat";
import { toHaveFainted } from "#test/test-utils/matchers/to-have-fainted";
@ -28,6 +29,7 @@ expect.extend({
toHaveTakenDamage,
toHaveWeather,
toHaveTerrain,
toHaveArenaTag,
toHaveFullHp,
toHaveStatusEffect,
toHaveStatStage,

View File

@ -0,0 +1,80 @@
import type { ArenaTag, ArenaTagTypeMap } from "#data/arena-tag";
import type { ArenaTagSide } from "#enums/arena-tag-side";
import { ArenaTagType } from "#enums/arena-tag-type";
import type { OneOther } from "#test/@types/test-helpers";
// biome-ignore lint/correctness/noUnusedImports: TSDoc
import type { GameManager } from "#test/test-utils/game-manager";
import { getEnumStr, getOnelineDiffStr, stringifyEnumArray } from "#test/test-utils/string-utils";
import { isGameManagerInstance, receivedStr } from "#test/test-utils/test-utils";
import type { NonFunctionPropertiesRecursive } from "#types/type-helpers";
import type { MatcherState, SyncExpectationResult } from "@vitest/expect";
export type toHaveArenaTagOptions<T extends ArenaTagType> = OneOther<ArenaTagTypeMap[T], "tagType">;
/**
* Matcher to check if the {@linkcode Arena} has a given {@linkcode ArenaTag} active.
* @param received - The object to check. Should be the current {@linkcode GameManager}.
* @param expectedType - The {@linkcode ArenaTagType} of the desired tag, or a partially-filled object
* containing the desired properties
* @param side - The {@linkcode ArenaTagSide | side of the field} the tag should affect, or
* {@linkcode ArenaTagSide.BOTH} to check both sides
* @returns The result of the matching
*/
export function toHaveArenaTag<T extends ArenaTagType>(
this: MatcherState,
received: unknown,
// simplified types used for brevity; full overloads are in `vitest.d.ts`
expectedType: T | (Partial<NonFunctionPropertiesRecursive<ArenaTag>> & { tagType: T; side: ArenaTagSide }),
side?: ArenaTagSide,
): SyncExpectationResult {
if (!isGameManagerInstance(received)) {
return {
pass: this.isNot,
message: () => `Expected to recieve a GameManager, but got ${receivedStr(received)}!`,
};
}
if (!received.scene?.arena) {
return {
pass: false,
message: () => `Expected GameManager.${received.scene ? "scene" : "scene.arena"} to be defined!`,
};
}
if (typeof expectedType === "string") {
// Coerce lone `tagType`s into objects
// Bangs are ok as we enforce safety via overloads
expectedType = { tagType: expectedType, side: side! };
}
// We need to get all tags for the case of checking properties of a tag present on both sides of the arena
const tags = received.scene.arena.findTagsOnSide(t => t.tagType === expectedType.tagType, expectedType.side);
if (!tags.length) {
const expectedStr = getEnumStr(ArenaTagType, expectedType.tagType);
return {
pass: false,
message: () => `Expected the arena to have a tag matching ${expectedStr}, but it didn't!`,
expected: getEnumStr(ArenaTagType, expectedType.tagType),
actual: stringifyEnumArray(
ArenaTagType,
received.scene.arena.tags.map(t => t.tagType),
),
};
}
// Pass if any of the matching tags meet our criteria
const pass = tags.some(tag =>
this.equals(tag, expectedType, [...this.customTesters, this.utils.subsetEquality, this.utils.iterableEquality]),
);
const expectedStr = getOnelineDiffStr.call(this, expectedType);
return {
pass,
message: () =>
pass
? `Expected the arena to NOT have a tag matching ${expectedStr}, but it did!`
: `Expected the arena to have a tag matching ${expectedStr}, but it didn't!`,
expected: expectedType,
actual: tags,
};
}

View File

@ -37,10 +37,8 @@ export function toHaveStatusEffect(
const actualEffect = received.status?.effect ?? StatusEffect.NONE;
// Check exclusively effect equality first, coercing non-matching status effects to numbers.
if (actualEffect !== (expectedStatus as Exclude<typeof expectedStatus, StatusEffect>)?.effect) {
// This is actually 100% safe as `expectedStatus?.effect` will evaluate to `undefined` if a StatusEffect was passed,
// which will never match actualEffect by definition
expectedStatus = (expectedStatus as Exclude<typeof expectedStatus, StatusEffect>).effect;
if (typeof expectedStatus === "object" && actualEffect !== expectedStatus.effect) {
expectedStatus = expectedStatus.effect;
}
if (typeof expectedStatus === "number") {