From 9455030fbea1241403a041cb214f9e39bad39047 Mon Sep 17 00:00:00 2001 From: Bertie690 Date: Sun, 3 Aug 2025 15:48:08 -0400 Subject: [PATCH] More improvements and minor fixes --- test/@types/vitest.d.ts | 18 ++++++---- .../matchers/to-equal-array-unsorted.ts | 13 +++---- .../matchers/to-have-effective-stat.ts | 6 ++-- test/test-utils/matchers/to-have-terrain.ts | 4 +-- test/test-utils/matchers/to-have-types.ts | 35 ++++++++++++------- test/test-utils/matchers/to-have-used-move.ts | 7 ++-- test/test-utils/matchers/to-have-used-pp.ts | 6 ++-- test/test-utils/matchers/to-have-weather.ts | 8 ++--- 8 files changed, 56 insertions(+), 41 deletions(-) diff --git a/test/@types/vitest.d.ts b/test/@types/vitest.d.ts index 409a874ba84..4af28bb6414 100644 --- a/test/@types/vitest.d.ts +++ b/test/@types/vitest.d.ts @@ -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( - expectedType: OneOther & { tagType: T }, // intersection required bc this doesn't preserve T + expectedType: OneOther & { tagType: T }, // intersection required to preserve T ): void; /** * Check whether the current {@linkcode Arena} contains the given {@linkcode ArenaTag}. diff --git a/test/test-utils/matchers/to-equal-array-unsorted.ts b/test/test-utils/matchers/to-equal-array-unsorted.ts index 846ea9e7779..97398689032 100644 --- a/test/test-utils/matchers/to-equal-array-unsorted.ts +++ b/test/test-utils/matchers/to-equal-array-unsorted.ts @@ -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); diff --git a/test/test-utils/matchers/to-have-effective-stat.ts b/test/test-utils/matchers/to-have-effective-stat.ts index bc10a646c02..fba285e49e7 100644 --- a/test/test-utils/matchers/to-have-effective-stat.ts +++ b/test/test-utils/matchers/to-have-effective-stat.ts @@ -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 { diff --git a/test/test-utils/matchers/to-have-terrain.ts b/test/test-utils/matchers/to-have-terrain.ts index 292c32abafc..29a56ceb23b 100644 --- a/test/test-utils/matchers/to-have-terrain.ts +++ b/test/test-utils/matchers/to-have-terrain.ts @@ -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!`, }; } diff --git a/test/test-utils/matchers/to-have-types.ts b/test/test-utils/matchers/to-have-types.ts index 3f16f740583..e7aab8e7d94 100644 --- a/test/test-utils/matchers/to-have-types.ts +++ b/test/test-utils/matchers/to-have-types.ts @@ -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); diff --git a/test/test-utils/matchers/to-have-used-move.ts b/test/test-utils/matchers/to-have-used-move.ts index ef90e4dbad9..3a3008fdf50 100644 --- a/test/test-utils/matchers/to-have-used-move.ts +++ b/test/test-utils/matchers/to-have-used-move.ts @@ -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, }; diff --git a/test/test-utils/matchers/to-have-used-pp.ts b/test/test-utils/matchers/to-have-used-pp.ts index 3b606a535bc..ce7f5b5ea23 100644 --- a/test/test-utils/matchers/to-have-used-pp.ts +++ b/test/test-utils/matchers/to-have-used-pp.ts @@ -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, diff --git a/test/test-utils/matchers/to-have-weather.ts b/test/test-utils/matchers/to-have-weather.ts index 49433b2137b..21a3a54c80d 100644 --- a/test/test-utils/matchers/to-have-weather.ts +++ b/test/test-utils/matchers/to-have-weather.ts @@ -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!`, }; }