diff --git a/test/@types/vitest.d.ts b/test/@types/vitest.d.ts index 61f8b422199..14a9f6d7533 100644 --- a/test/@types/vitest.d.ts +++ b/test/@types/vitest.d.ts @@ -11,6 +11,7 @@ import type { TerrainType } from "#app/data/terrain"; import type { WeatherType } from "#enums/weather-type"; import type { ToHaveEffectiveStatMatcherOptions } from "#test/test-utils/matchers/to-have-effective-stat"; import { TurnMove } from "#types/turn-move"; +import { expectedStatusType } from "#test/test-utils/matchers/to-have-status-effect-matcher"; declare module "vitest" { interface Assertion { @@ -31,7 +32,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; /** * Matcher to check the contents of a {@linkcode Pokemon}'s move history. @@ -64,9 +65,10 @@ declare module "vitest" { /** * Matcher to check if a {@linkcode Pokemon} has a specific {@linkcode StatusEffect | non-volatile status effect}. - * @param expectedStatusEffect - The expected {@linkcode StatusEffect} + * @param expectedStatusEffect - The {@linkcode StatusEffect} the Pokemon is expected to have, + * or a partially filled {@linkcode Status} containing the desired properties */ - toHaveStatusEffect(expectedStatusEffect: StatusEffect): void; + toHaveStatusEffect(expectedStatusEffect: expectedStatusType): void; /** * Matcher to check if the current {@linkcode WeatherType} is as expected. diff --git a/test/matchers.setup.ts b/test/matchers.setup.ts index 6bddbbe0567..f225daa27d3 100644 --- a/test/matchers.setup.ts +++ b/test/matchers.setup.ts @@ -5,7 +5,6 @@ import { toHaveEffectiveStatMatcher } from "#test/test-utils/matchers/to-have-ef import { toHaveFaintedMatcher } from "#test/test-utils/matchers/to-have-fainted"; import { toHaveFullHpMatcher } from "#test/test-utils/matchers/to-have-full-hp"; import { toHaveHpMatcher } from "#test/test-utils/matchers/to-have-hp"; -import { toHaveMoveResultMatcher } from "#test/test-utils/matchers/to-have-move-result-matcher"; import { toHaveStatStageMatcher } from "#test/test-utils/matchers/to-have-stat-stage-matcher"; import { toHaveStatusEffectMatcher } from "#test/test-utils/matchers/to-have-status-effect-matcher"; import { toHaveTakenDamageMatcher } from "#test/test-utils/matchers/to-have-taken-damage-matcher"; @@ -23,7 +22,6 @@ import { expect } from "vitest"; expect.extend({ toEqualArrayUnsorted, toHaveTypes, - toHaveMoveResult: toHaveMoveResultMatcher, toHaveUsedMove: toHaveUsedMoveMatcher, toHaveEffectiveStat: toHaveEffectiveStatMatcher, toHaveTakenDamage: toHaveTakenDamageMatcher, diff --git a/test/test-utils/matchers/a.test.ts b/test/test-utils/matchers/a.test.ts index 95278533534..f866131f99d 100644 --- a/test/test-utils/matchers/a.test.ts +++ b/test/test-utils/matchers/a.test.ts @@ -1,8 +1,43 @@ +import { AbilityId } from "#enums/ability-id"; +import { MoveId } from "#enums/move-id"; import { PokemonType } from "#enums/pokemon-type"; -import { describe, expect, it } from "vitest"; +import { SpeciesId } from "#enums/species-id"; +import { GameManager } from "#test/test-utils/game-manager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; -describe("a", () => { - it("r", () => { - expect(1).toHaveTypes([PokemonType.FLYING]); +describe("Utils - Fff", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .ability(AbilityId.BALL_FETCH) + .battleStyle("single") + .criticalHits(false) + .enemySpecies(SpeciesId.MAGIKARP) + .enemyAbility(AbilityId.BALL_FETCH) + .enemyMoveset(MoveId.SPLASH) + .startingLevel(100) + .enemyLevel(100); + }); + + it("should do XYZ", async () => { + await game.classicMode.startBattle([SpeciesId.FEEBAS]); + + expect(game.field.getPlayerPokemon()).toHaveTypes([PokemonType.WATER]); + expect.soft(game.field.getPlayerPokemon()).toHaveTypes([PokemonType.FLYING]); + expect.soft(game.field.getPlayerPokemon()).toHaveTypes([PokemonType.WATER]); }); }); diff --git a/test/test-utils/matchers/to-have-status-effect-matcher.ts b/test/test-utils/matchers/to-have-status-effect-matcher.ts index 41e0a25fa80..bf1ecdd458b 100644 --- a/test/test-utils/matchers/to-have-status-effect-matcher.ts +++ b/test/test-utils/matchers/to-have-status-effect-matcher.ts @@ -3,13 +3,12 @@ import type { Pokemon } from "#field/pokemon"; /* biome-ignore-end lint/correctness/noUnusedImports: tsdoc imports */ import { getPokemonNameWithAffix } from "#app/messages"; -import type { Status } from "#data/status-effect"; import { StatusEffect } from "#enums/status-effect"; +import { getEnumStr } from "#test/test-utils/string-utils"; import { isPokemonInstance, receivedStr } from "#test/test-utils/test-utils"; -import type { NonFunctionPropertiesRecursive } from "#types/type-helpers"; import type { MatcherState, SyncExpectationResult } from "@vitest/expect"; -export type expectedType = +export type expectedStatusType = | StatusEffect | { effect: StatusEffect.TOXIC; toxicTurnCount: number } | { effect: StatusEffect.SLEEP; sleepTurnsRemaining: number }; @@ -24,7 +23,7 @@ export type expectedType = export function toHaveStatusEffectMatcher( this: MatcherState, received: unknown, - expectedStatus: expectedType, + expectedStatus: expectedStatusType, ): SyncExpectationResult { if (!isPokemonInstance(received)) { return { @@ -33,19 +32,28 @@ export function toHaveStatusEffectMatcher( }; } - // Convert to Status - const expStatus: { effect: StatusEffect } & Partial> = - typeof expectedStatus === "number" - ? { - effect: expectedStatus, - } - : expectedStatus; + const pkmName = getPokemonNameWithAffix(received); - // If expected to have no status, - if (expStatus.effect === StatusEffect.NONE) { - k; + // Check exclusively effect equality + if (typeof expectedStatus === "number" || received.status?.effect !== expectedStatus.effect) { + const actualEffect = received.status?.effect ?? StatusEffect.NONE; + const pass = this.equals(actualEffect, expectedStatus, [...this.customTesters, this.utils.iterableEquality]); + + const actualStr = getEnumStr(StatusEffect, actualEffect, { prefix: "StatusEffect." }); + const expectedStr = getEnumStr(StatusEffect, actualEffect, { prefix: "StatusEffect." }); + + return { + pass, + message: () => + pass + ? `Expected ${pkmName} NOT to have ${expectedStr}, but it did!` + : `Expected ${pkmName} to have status effect ${expectedStr}, but got ${actualStr} instead!`, + expected: expectedStatus, + actual: actualEffect, + }; } + // Check for equality of all fields (for toxic turn count) const actualStatus = received.status; const pass = this.equals(received, expectedStatus, [ ...this.customTesters, @@ -53,13 +61,13 @@ export function toHaveStatusEffectMatcher( this.utils.iterableEquality, ]); - const pkmName = getPokemonNameWithAffix(received); - return { pass, message: () => pass - ? `Expected ${pkmName} NOT to have ${expectedStatusEffectStr}, but it did!` - : `Expected ${pkmName} to have status effect: ${expectedStatusEffectStr}, but got: ${actualStatusEffectStr}!`, + ? `Expected ${pkmName}'s status NOT to match ${this.utils.stringify(expectedStatus)}, but it did!` + : `Expected ${pkmName}'s status to match ${this.utils.stringify(expectedStatus)}, but got ${this.utils.stringify(actualStatus)} instead!`, + expected: expectedStatus, + actual: actualStatus, }; } diff --git a/test/test-utils/matchers/to-have-types.ts b/test/test-utils/matchers/to-have-types.ts index 4016e1b4ab7..59f43d9fd76 100644 --- a/test/test-utils/matchers/to-have-types.ts +++ b/test/test-utils/matchers/to-have-types.ts @@ -26,7 +26,7 @@ export interface toHaveTypesOptions { export function toHaveTypes( this: MatcherState, received: unknown, - expected: unknown, + expected: [PokemonType, ...PokemonType[]], options: toHaveTypesOptions = {}, ): SyncExpectationResult { if (!isPokemonInstance(received)) { @@ -36,32 +36,21 @@ export function toHaveTypes( }; } - if (!Array.isArray(expected) || expected.length === 0) { - return { - pass: false, - message: () => `Expected to receive an array with length >=1, but got ${this.utils.stringify(expected)}!`, - }; - } + const actualTypes = received.getTypes(...(options.args ?? [])).sort(); + const expectedTypes = expected.slice().sort(); - if (!expected.every((t): t is PokemonType => t in PokemonType)) { - return { - pass: false, - message: () => `Expected to receive array of PokemonTypes but got ${this.utils.stringify(expected)}!`, - }; - } - - const actualSorted = stringifyEnumArray(PokemonType, received.getTypes(...(options.args ?? [])).sort()); - const expectedSorted = stringifyEnumArray(PokemonType, expected.slice().sort()); + const actualStr = stringifyEnumArray(PokemonType, actualTypes); + const expectedStr = stringifyEnumArray(PokemonType, expectedTypes); + // 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 pass = this.equals(actualSorted, expectedSorted, matchers); + const pass = this.equals(actualStr, expectedStr, matchers); return { pass, - message: () => - `Expected ${getPokemonNameWithAffix(received)} to have types ${this.utils.stringify(expectedSorted)}, but got ${actualSorted}!`, - actual: actualSorted, - expected: expectedSorted, + message: () => `Expected ${getPokemonNameWithAffix(received)} to have types ${expectedStr}, but got ${actualStr}!`, + actual: actualTypes, + expected: expectedTypes, }; } diff --git a/test/test-utils/string-utils.ts b/test/test-utils/string-utils.ts index 1b0999237f3..92342ceb036 100644 --- a/test/test-utils/string-utils.ts +++ b/test/test-utils/string-utils.ts @@ -25,7 +25,7 @@ interface getEnumStrOptions { * @param obj - The {@linkcode EnumOrObject} to source reverse mappings from * @param enums - One of {@linkcode obj}'s values * @param casing - A string denoting the casing method to use; default `Preserve` - * @param suffix - An optional string to be prepended to the enum's string representation. + * @param prefix - An optional string to be prepended to the enum's string representation. * @param suffix - An optional string to be appended to the enum's string representation. * @returns The stringified representation of `val` as dictated by the options. * @example @@ -38,7 +38,6 @@ interface getEnumStrOptions { * console.log(getEnumStr(fakeEnum, fakeEnum.ONE)); // Output: "ONE (=1)" * console.log(getEnumStr(fakeEnum, fakeEnum.TWO, {case: "Title", suffix: " Terrain"})); // Output: "Two Terrain (=2)" * ``` - */ export function getEnumStr( obj: E,