More improvements and minor fixes

This commit is contained in:
Bertie690 2025-08-03 15:48:08 -04:00
parent dbea701d6d
commit 9455030fbe
8 changed files with 56 additions and 41 deletions

View File

@ -10,7 +10,7 @@ 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 { toHaveEffectiveStatOptions } 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";
@ -40,10 +40,16 @@ declare module "vitest" {
* Check whether 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 options - The {@linkcode toHaveTypesOptions | options} passed to the matcher
*/
toHaveTypes(expected: [PokemonType, ...PokemonType[]], options?: toHaveTypesOptions): void;
/**
* Check whether a {@linkcode Pokemon}'s current typing includes the given types.
*
* @param expected - The expected types (in any order)
* @param options - The {@linkcode toHaveTypesOptions | options} passed to the matcher
*/
toHaveTypes(expected: PokemonType[], options?: toHaveTypesOptions): void;
toHaveTypes(expected: [PokemonType, ...PokemonType[]], options?: toHaveTypesOptions): void;
/**
* Matcher to check the contents of a {@linkcode Pokemon}'s move history.
@ -62,11 +68,11 @@ declare module "vitest" {
*
* @param stat - The {@linkcode EffectiveStat} to check
* @param expectedValue - The expected value of {@linkcode stat}
* @param options - (Optional) The {@linkcode ToHaveEffectiveStatMatcherOptions}
* @param options - The {@linkcode toHaveEffectiveStatOptions | options} passed to the matcher
* @remarks
* If you want to check the stat **before** modifiers are applied, use {@linkcode Pokemon.getStat} instead.
*/
toHaveEffectiveStat(stat: EffectiveStat, expectedValue: number, options?: ToHaveEffectiveStatMatcherOptions): void;
toHaveEffectiveStat(stat: EffectiveStat, expectedValue: number, options?: toHaveEffectiveStatOptions): void;
/**
* Check whether a {@linkcode Pokemon} has taken a specific amount of damage.
@ -93,7 +99,7 @@ declare module "vitest" {
* @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
expectedType: OneOther<ArenaTagTypeMap[T], "tagType" | "side"> & { tagType: T }, // intersection required to preserve T
): void;
/**
* Check whether the current {@linkcode Arena} contains the given {@linkcode ArenaTag}.

View File

@ -1,4 +1,5 @@
import { getOnelineDiffStr } from "#test/test-utils/string-utils";
import { receivedStr } from "#test/test-utils/test-utils";
import type { MatcherState, SyncExpectationResult } from "@vitest/expect";
/**
@ -14,22 +15,22 @@ export function toEqualArrayUnsorted(
): SyncExpectationResult {
if (!Array.isArray(received)) {
return {
pass: false,
message: () => `Expected an array, but got ${this.utils.stringify(received)}!`,
pass: this.isNot,
message: () => `Expected to receive an array, but got ${receivedStr(received)}!`,
};
}
if (received.length !== expected.length) {
return {
pass: false,
message: () => `Expected to receive array of length ${received.length}, but got ${expected.length} instead!`,
actual: received,
message: () => `Expected to receive an array of length ${received.length}, but got ${expected.length} instead!`,
expected,
actual: received,
};
}
const actualSorted = received.slice().sort();
const expectedSorted = expected.slice().sort();
const actualSorted = received.toSorted();
const expectedSorted = expected.toSorted();
const pass = this.equals(actualSorted, expectedSorted, [...this.customTesters, this.utils.iterableEquality]);
const actualStr = getOnelineDiffStr.call(this, actualSorted);

View File

@ -6,7 +6,7 @@ import { getStatName } from "#test/test-utils/string-utils";
import { isPokemonInstance, receivedStr } from "#test/test-utils/test-utils";
import type { MatcherState, SyncExpectationResult } from "@vitest/expect";
export interface ToHaveEffectiveStatMatcherOptions {
export interface toHaveEffectiveStatOptions {
/**
* The target {@linkcode Pokemon}
* @see {@linkcode Pokemon.getEffectiveStat}
@ -30,7 +30,7 @@ export interface ToHaveEffectiveStatMatcherOptions {
* @param received - The object to check. Should be a {@linkcode Pokemon}
* @param stat - The {@linkcode EffectiveStat} to check
* @param expectedValue - The expected value of the {@linkcode stat}
* @param options - The {@linkcode ToHaveEffectiveStatMatcherOptions}
* @param options - The {@linkcode toHaveEffectiveStatOptions}
* @returns Whether the matcher passed
*/
export function toHaveEffectiveStat(
@ -38,7 +38,7 @@ export function toHaveEffectiveStat(
received: unknown,
stat: EffectiveStat,
expectedValue: number,
{ enemy, move, isCritical = false }: ToHaveEffectiveStatMatcherOptions = {},
{ enemy, move, isCritical = false }: toHaveEffectiveStatOptions = {},
): SyncExpectationResult {
if (!isPokemonInstance(received)) {
return {

View File

@ -21,14 +21,14 @@ export function toHaveTerrain(
if (!isGameManagerInstance(received)) {
return {
pass: false,
message: () => `Expected GameManager, but got ${receivedStr(received)}!`,
message: () => `Expected to receive a GameManager, but got ${receivedStr(received)}!`,
};
}
if (!received.scene?.arena) {
return {
pass: false,
message: () => `Expected GameManager.${received.scene ? "scene" : "scene.arena"} to be defined!`,
message: () => `Expected GameManager.${received.scene ? "scene.arena" : "scene"} to be defined!`,
};
}

View File

@ -7,10 +7,16 @@ import { isPokemonInstance, receivedStr } from "../test-utils";
export interface toHaveTypesOptions {
/**
* Whether to enforce exact matches (`true`) or superset matches (`false`).
* @defaultValue `true`
* Value dictating the strength of the enforced typing match.
*
* Possible values (in ascending order of strength) are:
* - `"ordered"`: Enforce that the {@linkcode Pokemon}'s types are identical **and in the same order**
* - `"unordered"`: Enforce that the {@linkcode Pokemon}'s types are identical **without checking order**
* - `"superset"`: Enforce that the {@linkcode Pokemon}'s types are **a superset of** the expected types
* (all must be present, but extras can be there)
* @defaultValue `"unordered"`
*/
exact?: boolean;
mode?: "ordered" | "unordered" | "superset";
/**
* Optional arguments to pass to {@linkcode Pokemon.getTypes}.
*/
@ -18,31 +24,34 @@ export interface toHaveTypesOptions {
}
/**
* Matcher that checks if an array contains exactly the given items, disregarding order.
* @param received - The object to check. Should be an array of one or more {@linkcode PokemonType}s.
* @param options - The {@linkcode toHaveTypesOptions | options} for this matcher
* Matcher that checks if a {@linkcode Pokemon}'s typing is as expected.
* @param received - The object to check. Should be a {@linkcode Pokemon}
* @param expected - An array of one or more {@linkcode PokemonType}s to compare against.
* @param mode - The mode to perform the matching;
* @returns The result of the matching
*/
export function toHaveTypes(
this: MatcherState,
received: unknown,
expected: [PokemonType, ...PokemonType[]],
options: toHaveTypesOptions = {},
{ mode = "unordered", args = [] }: toHaveTypesOptions = {},
): SyncExpectationResult {
if (!isPokemonInstance(received)) {
return {
pass: false,
pass: this.isNot,
message: () => `Expected to recieve a Pokémon, but got ${receivedStr(received)}!`,
};
}
const actualTypes = received.getTypes(...(options.args ?? [])).sort();
const expectedTypes = expected.slice().sort();
// Avoid sorting the types if strict ordering is desired
const actualTypes = mode === "ordered" ? received.getTypes(...args) : received.getTypes(...args).toSorted();
const expectedTypes = mode === "ordered" ? expected : expected.toSorted();
// Exact matches do not care about subset equality
const matchers = options.exact
? [...this.customTesters, this.utils.iterableEquality]
: [...this.customTesters, this.utils.subsetEquality, this.utils.iterableEquality];
const matchers =
mode === "superset"
? [...this.customTesters, this.utils.iterableEquality]
: [...this.customTesters, this.utils.subsetEquality, this.utils.iterableEquality];
const pass = this.equals(actualTypes, expectedTypes, matchers);
const actualStr = stringifyEnumArray(PokemonType, actualTypes);

View File

@ -27,7 +27,7 @@ export function toHaveUsedMove(
): SyncExpectationResult {
if (!isPokemonInstance(received)) {
return {
pass: false,
pass: this.isNot,
message: () => `Expected to receive a Pokémon, but got ${receivedStr(received)}!`,
};
}
@ -37,7 +37,7 @@ export function toHaveUsedMove(
if (move === undefined) {
return {
pass: false,
pass: this.isNot,
message: () => `Expected ${pkmName} to have used ${index + 1} moves, but it didn't!`,
actual: received.getLastXMoves(-1),
};
@ -62,8 +62,7 @@ export function toHaveUsedMove(
message: () =>
pass
? `Expected ${pkmName}'s ${moveIndexStr} to NOT match ${expectedStr}, but it did!`
: // Replace newlines with spaces to preserve one-line ness
`Expected ${pkmName}'s ${moveIndexStr} to match ${expectedStr}, but it didn't!`,
: `Expected ${pkmName}'s ${moveIndexStr} to match ${expectedStr}, but it didn't!`,
expected: expectedResult,
actual: move,
};

View File

@ -28,7 +28,7 @@ export function toHaveUsedPP(
): SyncExpectationResult {
if (!isPokemonInstance(received)) {
return {
pass: false,
pass: this.isNot,
message: () => `Expected to receive a Pokémon, but got ${receivedStr(received)}!`,
};
}
@ -36,7 +36,7 @@ export function toHaveUsedPP(
const override = received.isPlayer() ? Overrides.MOVESET_OVERRIDE : Overrides.OPP_MOVESET_OVERRIDE;
if (coerceArray(override).length > 0) {
return {
pass: false,
pass: this.isNot,
message: () =>
`Cannot test for PP consumption with ${received.isPlayer() ? "player" : "enemy"} moveset overrides active!`,
};
@ -48,7 +48,7 @@ export function toHaveUsedPP(
const movesetMoves = received.getMoveset().filter(pm => pm.moveId === expectedMove);
if (movesetMoves.length !== 1) {
return {
pass: false,
pass: this.isNot,
message: () =>
`Expected MoveId.${moveStr} to appear in ${pkmName}'s moveset exactly once, but got ${movesetMoves.length} times!`,
expected: expectedMove,

View File

@ -20,15 +20,15 @@ export function toHaveWeather(
): SyncExpectationResult {
if (!isGameManagerInstance(received)) {
return {
pass: false,
message: () => `Expected GameManager, but got ${receivedStr(received)}!`,
pass: this.isNot,
message: () => `Expected to receive a GameManager, but got ${receivedStr(received)}!`,
};
}
if (!received.scene?.arena) {
return {
pass: false,
message: () => `Expected GameManager.${received.scene ? "scene" : "scene.arena"} to be defined!`,
pass: this.isNot,
message: () => `Expected GameManager.${received.scene ? "scene.arena" : "scene"} to be defined!`,
};
}