mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-08-15 20:09:30 +02:00
Partially ported over pkty matchers (WIP)
This commit is contained in:
parent
7cb2c560ab
commit
917fb596b4
@ -41,7 +41,7 @@ export type Mutable<T> = {
|
||||
* @typeParam O - The type of the object
|
||||
* @typeParam V - The type of one of O's values
|
||||
*/
|
||||
export type InferKeys<O extends Record<keyof any, unknown>, V extends EnumValues<O>> = {
|
||||
export type InferKeys<O, V extends EnumValues<O>> = {
|
||||
[K in keyof O]: O[K] extends V ? K : never;
|
||||
}[keyof O];
|
||||
|
||||
|
98
test/@types/vitest.d.ts
vendored
98
test/@types/vitest.d.ts
vendored
@ -2,19 +2,29 @@ import type { Pokemon } from "#field/pokemon";
|
||||
import type { PokemonType } from "#enums/pokemon-type";
|
||||
import type { expect } from "vitest";
|
||||
import type { toHaveTypesOptions } from "#test/test-utils/matchers/to-have-types";
|
||||
import type { AbilityId } from "#enums/ability-id";
|
||||
import type { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import type { MoveId } from "#enums/move-id";
|
||||
import type { BattleStat, EffectiveStat, Stat } from "#enums/stat";
|
||||
import type { StatusEffect } from "#enums/status-effect";
|
||||
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";
|
||||
|
||||
declare module "vitest" {
|
||||
interface Assertion {
|
||||
/**
|
||||
* Matcher to check if an array contains EXACTLY the given items (in any order).
|
||||
*
|
||||
* Different from {@linkcode expect.arrayContaining} as the latter only requires the array contain
|
||||
* _at least_ the listed items.
|
||||
* Different from {@linkcode expect.arrayContaining} as the latter only checks for subset equality
|
||||
* (as opposed to full equality)
|
||||
*
|
||||
* @param expected - The expected contents of the array, in any order.
|
||||
* @param expected - The expected contents of the array, in any order
|
||||
* @see {@linkcode expect.arrayContaining}
|
||||
*/
|
||||
toEqualArrayUnsorted<E>(expected: E[]): void;
|
||||
|
||||
/**
|
||||
* Matcher to check if a {@linkcode Pokemon}'s current typing includes the given types.
|
||||
*
|
||||
@ -22,5 +32,87 @@ declare module "vitest" {
|
||||
* @param options - The options passed to the matcher.
|
||||
*/
|
||||
toHaveTypes(expected: PokemonType[], options?: toHaveTypesOptions): void;
|
||||
|
||||
/**
|
||||
* Matcher to check the contents of a {@linkcode Pokemon}'s move history.
|
||||
*
|
||||
* @param expectedValue - The expected value; can be a {@linkcode MoveId} or a partially filled {@linkcode TurnMove}
|
||||
* @param index - The index of the move history entry to check, in order from most recent to least recent.
|
||||
* Default `0` (last used move)
|
||||
* @see {@linkcode Pokemon.getLastXMoves}
|
||||
*/
|
||||
toHaveUsedMove(expected: MoveId | Partial<TurnMove>, index?: number): void;
|
||||
|
||||
/**
|
||||
* Matcher to check if a {@linkcode Pokemon Pokemon's} effective stat is as expected
|
||||
* (checked after all mstat value modifications).
|
||||
*
|
||||
* @param stat - The {@linkcode EffectiveStat} to check
|
||||
* @param expectedValue - The expected value of {@linkcode stat}
|
||||
* @param options - (Optional) The {@linkcode ToHaveEffectiveStatMatcherOptions}
|
||||
* @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;
|
||||
|
||||
/**
|
||||
* Matcher to check if a {@linkcode Pokemon} has taken a specific amount of damage.
|
||||
* @param expectedDamageTaken - The expected amount of damage taken
|
||||
* @param roundDown - Whether to round down @linkcode expectedDamageTaken} with {@linkcode toDmgValue}; default `true`
|
||||
*/
|
||||
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 expected {@linkcode StatusEffect}
|
||||
*/
|
||||
toHaveStatusEffect(expectedStatusEffect: StatusEffect): void;
|
||||
|
||||
/**
|
||||
* Matcher to check if the current {@linkcode WeatherType} is as expected.
|
||||
* @param expectedWeatherType - The expected {@linkcode WeatherType}
|
||||
*/
|
||||
toHaveWeather(expectedWeatherType: WeatherType): void;
|
||||
|
||||
/**
|
||||
* Matcher to check if the current {@linkcode TerrainType} is as expected.
|
||||
* @param expectedTerrainType - The expected {@linkcode TerrainType}
|
||||
*/
|
||||
toHaveTerrain(expectedTerrainType: TerrainType): void;
|
||||
|
||||
/**
|
||||
* Matcher to check if a {@linkcode Pokemon} has full HP.
|
||||
*/
|
||||
toHaveFullHp(): void;
|
||||
|
||||
/**
|
||||
* Matcher to check if a {@linkcode Pokemon} has a specific {@linkcode Stat} stage.
|
||||
* @param stat - The {@linkcode BattleStat} to check
|
||||
* @param expectedStage - The expected stat stage value of {@linkcode stat}
|
||||
*/
|
||||
toHaveStatStage(stat: BattleStat, expectedStage: number): void;
|
||||
|
||||
/**
|
||||
* Matcher to check if a {@linkcode Pokemon} has a specific {@linkcode BattlerTagType}.
|
||||
* @param expectedBattlerTagType - The expected {@linkcode BattlerTagType}
|
||||
*/
|
||||
toHaveBattlerTag(expectedBattlerTagType: BattlerTagType): void;
|
||||
|
||||
/**
|
||||
* Matcher to check if a {@linkcode Pokemon} had a specific {@linkcode AbilityId} applied.
|
||||
* @param expectedAbilityId - The expected {@linkcode AbilityId}
|
||||
*/
|
||||
toHaveAbilityApplied(expectedAbilityId: AbilityId): void;
|
||||
|
||||
/**
|
||||
* Matcher to check if a {@linkcode Pokemon} has a specific amount of HP.
|
||||
* @param expectedHp - The expected amount of {@linkcode Stat.HP | HP} to have
|
||||
*/
|
||||
toHaveHp(expectedHp: number): void;
|
||||
|
||||
/**
|
||||
* Matcher to check if a {@linkcode Pokemon} has fainted (as determined by {@linkcode Pokemon.isFainted}).
|
||||
*/
|
||||
toHaveFainted(): void;
|
||||
}
|
||||
}
|
@ -1,5 +1,18 @@
|
||||
import { toEqualArrayUnsorted } from "#test/test-utils/matchers/to-equal-array-unsorted";
|
||||
import { toHaveAbilityAppliedMatcher } from "#test/test-utils/matchers/to-have-ability-applied";
|
||||
import { toHaveBattlerTag } from "#test/test-utils/matchers/to-have-battler-tag";
|
||||
import { toHaveEffectiveStatMatcher } from "#test/test-utils/matchers/to-have-effective-stat";
|
||||
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";
|
||||
import { toHaveTerrainMatcher } from "#test/test-utils/matchers/to-have-terrain-matcher";
|
||||
import { toHaveTypes } from "#test/test-utils/matchers/to-have-types";
|
||||
import { toHaveUsedMoveMatcher } from "#test/test-utils/matchers/to-have-used-move-matcher";
|
||||
import { toHaveWeatherMatcher } from "#test/test-utils/matchers/to-have-weather-matcher";
|
||||
import { expect } from "vitest";
|
||||
|
||||
/*
|
||||
@ -10,4 +23,17 @@ import { expect } from "vitest";
|
||||
expect.extend({
|
||||
toEqualArrayUnsorted,
|
||||
toHaveTypes,
|
||||
toHaveMoveResult: toHaveMoveResultMatcher,
|
||||
toHaveUsedMove: toHaveUsedMoveMatcher,
|
||||
toHaveEffectiveStat: toHaveEffectiveStatMatcher,
|
||||
toHaveTakenDamage: toHaveTakenDamageMatcher,
|
||||
toHaveWeather: toHaveWeatherMatcher,
|
||||
toHaveTerrain: toHaveTerrainMatcher,
|
||||
toHaveFullHp: toHaveFullHpMatcher,
|
||||
toHaveStatusEffect: toHaveStatusEffectMatcher,
|
||||
toHaveStatStage: toHaveStatStageMatcher,
|
||||
toHaveBattlerTag: toHaveBattlerTag,
|
||||
toHaveAbilityApplied: toHaveAbilityAppliedMatcher,
|
||||
toHaveHp: toHaveHpMatcher,
|
||||
toHaveFainted: toHaveFaintedMatcher,
|
||||
});
|
||||
|
8
test/test-utils/matchers/a.test.ts
Normal file
8
test/test-utils/matchers/a.test.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { PokemonType } from "#enums/pokemon-type";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
describe("a", () => {
|
||||
it("r", () => {
|
||||
expect(1).toHaveTypes([PokemonType.FLYING]);
|
||||
});
|
||||
});
|
@ -2,28 +2,29 @@ import type { MatcherState, SyncExpectationResult } from "@vitest/expect";
|
||||
|
||||
/**
|
||||
* Matcher to check if an array contains exactly the given items, disregarding order.
|
||||
* @param received - The object to check. Should be an array of elements.
|
||||
* @returns The result of the matching
|
||||
* @param received - The received value. Should be an array of elements
|
||||
* @param expected - The array to check equality with
|
||||
* @returns Whether the matcher passed
|
||||
*/
|
||||
export function toEqualArrayUnsorted(this: MatcherState, received: unknown, expected: unknown): SyncExpectationResult {
|
||||
if (!Array.isArray(received)) {
|
||||
return {
|
||||
pass: this.isNot,
|
||||
pass: false,
|
||||
message: () => `Expected an array, but got ${this.utils.stringify(received)}!`,
|
||||
};
|
||||
}
|
||||
|
||||
if (!Array.isArray(expected)) {
|
||||
return {
|
||||
pass: this.isNot,
|
||||
message: () => `Expected to recieve an array, but got ${this.utils.stringify(expected)}!`,
|
||||
pass: false,
|
||||
message: () => `Expected to receive an array, but got ${this.utils.stringify(expected)}!`,
|
||||
};
|
||||
}
|
||||
|
||||
if (received.length !== expected.length) {
|
||||
return {
|
||||
pass: this.isNot,
|
||||
message: () => `Expected to recieve array of length ${received.length}, but got ${expected.length}!`,
|
||||
pass: false,
|
||||
message: () => `Expected to receive array of length ${received.length}, but got ${expected.length}!`,
|
||||
actual: received,
|
||||
expected,
|
||||
};
|
||||
@ -34,7 +35,7 @@ export function toEqualArrayUnsorted(this: MatcherState, received: unknown, expe
|
||||
const pass = this.equals(gotSorted, wantSorted, [...this.customTesters, this.utils.iterableEquality]);
|
||||
|
||||
return {
|
||||
pass: this.isNot !== pass,
|
||||
pass,
|
||||
message: () =>
|
||||
`Expected ${this.utils.stringify(received)} to exactly equal ${this.utils.stringify(expected)} without order!`,
|
||||
actual: gotSorted,
|
||||
|
42
test/test-utils/matchers/to-have-ability-applied.ts
Normal file
42
test/test-utils/matchers/to-have-ability-applied.ts
Normal file
@ -0,0 +1,42 @@
|
||||
/* biome-ignore-start lint/correctness/noUnusedImports: tsdoc imports */
|
||||
import type { Pokemon } from "#field/pokemon";
|
||||
/* biome-ignore-end lint/correctness/noUnusedImports: tsdoc imports */
|
||||
|
||||
import { getPokemonNameWithAffix } from "#app/messages";
|
||||
import { AbilityId } from "#enums/ability-id";
|
||||
import { isPokemonInstance, receivedStr } from "#test/test-utils/test-utils";
|
||||
import type { MatcherState, SyncExpectationResult } from "@vitest/expect";
|
||||
|
||||
/**
|
||||
* Matcher to check if a {@linkcode Pokemon} had a specific {@linkcode AbilityId} applied.
|
||||
* @param received - The object to check. Should be a {@linkcode Pokemon}.
|
||||
* @param expectedAbility - The {@linkcode AbilityId} to check for.
|
||||
* @returns Whether the matcher passed
|
||||
*/
|
||||
export function toHaveAbilityAppliedMatcher(
|
||||
this: MatcherState,
|
||||
received: unknown,
|
||||
expectedAbilityId: AbilityId,
|
||||
): SyncExpectationResult {
|
||||
if (!isPokemonInstance(received)) {
|
||||
return {
|
||||
pass: false,
|
||||
message: () => `Expected to recieve a Pokemon, but got ${receivedStr(received)}!`,
|
||||
};
|
||||
}
|
||||
|
||||
const pass = received.waveData.abilitiesApplied.has(expectedAbilityId);
|
||||
|
||||
const pkmName = getPokemonNameWithAffix(received);
|
||||
const expectedAbilityStr = `${AbilityId[expectedAbilityId]} (=${expectedAbilityId})`;
|
||||
|
||||
return {
|
||||
pass,
|
||||
message: () =>
|
||||
pass
|
||||
? `Expected ${pkmName} to NOT have applied ${expectedAbilityStr}, but it did!`
|
||||
: `Expected ${pkmName} to have applied ${expectedAbilityStr}, but it did not!`,
|
||||
actual: received.waveData.abilitiesApplied,
|
||||
expected: expectedAbilityId,
|
||||
};
|
||||
}
|
47
test/test-utils/matchers/to-have-battler-tag.ts
Normal file
47
test/test-utils/matchers/to-have-battler-tag.ts
Normal file
@ -0,0 +1,47 @@
|
||||
/* biome-ignore-start lint/correctness/noUnusedImports: tsdoc imports */
|
||||
import type { Pokemon } from "#field/pokemon";
|
||||
/* biome-ignore-end lint/correctness/noUnusedImports: tsdoc imports */
|
||||
|
||||
import { getPokemonNameWithAffix } from "#app/messages";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { getEnumStr, stringifyEnumArray } from "#test/test-utils/string-utils";
|
||||
import { isPokemonInstance, receivedStr } from "#test/test-utils/test-utils";
|
||||
import type { MatcherState, SyncExpectationResult } from "@vitest/expect";
|
||||
|
||||
/**
|
||||
* Matcher to check if a {@linkcode Pokemon} has a specific {@linkcode BattlerTagType}.
|
||||
* @param received - The object to check. Should be a {@linkcode Pokemon}
|
||||
* @param expectedBattlerTagType - The {@linkcode BattlerTagType} to check for
|
||||
* @returns Whether the matcher passed
|
||||
*/
|
||||
export function toHaveBattlerTag(
|
||||
this: MatcherState,
|
||||
received: unknown,
|
||||
expectedBattlerTagType: BattlerTagType,
|
||||
): SyncExpectationResult {
|
||||
if (!isPokemonInstance(received)) {
|
||||
return {
|
||||
pass: this.isNot,
|
||||
message: () => `Expected to receive a Pokémon, but got ${receivedStr(received)}!`,
|
||||
};
|
||||
}
|
||||
|
||||
const pass = !!received.getTag(expectedBattlerTagType);
|
||||
const pkmName = getPokemonNameWithAffix(received);
|
||||
// "the SEEDED BattlerTag (=1)"
|
||||
const expectedTagStr = getEnumStr(BattlerTagType, expectedBattlerTagType, { prefix: "the ", suffix: " BattlerTag" });
|
||||
const actualTagStr = stringifyEnumArray(
|
||||
BattlerTagType,
|
||||
received.summonData.tags.map(t => t.tagType),
|
||||
);
|
||||
|
||||
return {
|
||||
pass,
|
||||
message: () =>
|
||||
pass
|
||||
? `Expected ${pkmName} to NOT have ${expectedTagStr}, but it did!`
|
||||
: `Expected ${pkmName} to have ${expectedTagStr}, but it did not!`,
|
||||
actual: actualTagStr,
|
||||
expected: getEnumStr(BattlerTagType, expectedBattlerTagType),
|
||||
};
|
||||
}
|
65
test/test-utils/matchers/to-have-effective-stat.ts
Normal file
65
test/test-utils/matchers/to-have-effective-stat.ts
Normal file
@ -0,0 +1,65 @@
|
||||
import { getPokemonNameWithAffix } from "#app/messages";
|
||||
import { type EffectiveStat, getStatKey } from "#enums/stat";
|
||||
import type { Pokemon } from "#field/pokemon";
|
||||
import type { Move } from "#moves/move";
|
||||
import { isPokemonInstance, receivedStr } from "#test/test-utils/test-utils";
|
||||
import type { MatcherState, SyncExpectationResult } from "@vitest/expect";
|
||||
import i18next from "i18next";
|
||||
|
||||
export interface ToHaveEffectiveStatMatcherOptions {
|
||||
/**
|
||||
* The target {@linkcode Pokemon}
|
||||
* @see {@linkcode Pokemon#getEffectiveStat}
|
||||
*/
|
||||
enemy?: Pokemon;
|
||||
/**
|
||||
* The {@linkcode Move} being used
|
||||
* @see {@linkcode Pokemon#getEffectiveStat}
|
||||
*/
|
||||
move?: Move;
|
||||
/**
|
||||
* Whether a critical hit occurred or not
|
||||
* @see {@linkcode Pokemon#getEffectiveStat}
|
||||
* @defaultValue `false`
|
||||
*/
|
||||
isCritical?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Matcher to check if a {@linkcode Pokemon}'s effective stat equals the expected value
|
||||
* @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}
|
||||
* @returns Whether the matcher passed
|
||||
*/
|
||||
export function toHaveEffectiveStatMatcher(
|
||||
this: MatcherState,
|
||||
received: unknown,
|
||||
stat: EffectiveStat,
|
||||
expectedValue: number,
|
||||
{ enemy, move, isCritical = false }: ToHaveEffectiveStatMatcherOptions = {},
|
||||
): SyncExpectationResult {
|
||||
if (!isPokemonInstance(received)) {
|
||||
return {
|
||||
pass: false,
|
||||
message: () => `Expected to receive a Pokémon, but got ${receivedStr(received)}!`,
|
||||
};
|
||||
}
|
||||
|
||||
const actualValue = received.getEffectiveStat(stat, enemy, move, undefined, undefined, undefined, isCritical);
|
||||
const pass = actualValue === expectedValue;
|
||||
|
||||
const pkmName = getPokemonNameWithAffix(received);
|
||||
const statName = i18next.t(getStatKey(stat));
|
||||
|
||||
return {
|
||||
pass,
|
||||
message: () =>
|
||||
pass
|
||||
? `Expected ${pkmName} to NOT have ${expectedValue} ${statName}, but it did!`
|
||||
: `Expected ${pkmName} to have ${expectedValue} ${statName}, but got ${actualValue} instead!`,
|
||||
expected: expectedValue,
|
||||
actual: actualValue,
|
||||
};
|
||||
}
|
31
test/test-utils/matchers/to-have-fainted.ts
Normal file
31
test/test-utils/matchers/to-have-fainted.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import { getPokemonNameWithAffix } from "#app/messages";
|
||||
import { isPokemonInstance, receivedStr } from "#test/test-utils/test-utils";
|
||||
import type { MatcherState, SyncExpectationResult } from "@vitest/expect";
|
||||
|
||||
/**
|
||||
* Matcher to check if a Pokemon has fainted
|
||||
* @param received - The object to check. Should be a {@linkcode Pokemon}.
|
||||
* @returns Whether the matcher passed
|
||||
*/
|
||||
export function toHaveFaintedMatcher(this: MatcherState, received: unknown): SyncExpectationResult {
|
||||
if (!isPokemonInstance(received)) {
|
||||
return {
|
||||
pass: false,
|
||||
message: () => `Expected to receive a Pokémon, but got ${receivedStr(received)}!`,
|
||||
};
|
||||
}
|
||||
|
||||
const { hp } = received;
|
||||
const maxHp = received.getMaxHp();
|
||||
const pass = received.isFainted();
|
||||
|
||||
const pkmName = getPokemonNameWithAffix(received);
|
||||
|
||||
return {
|
||||
pass,
|
||||
message: () =>
|
||||
pass
|
||||
? `Expected ${pkmName} NOT to have fainted, but it did! (${hp}/${maxHp} HP)`
|
||||
: `Expected ${pkmName} to have fainted, but it did not. (${hp}/${maxHp} HP)`,
|
||||
};
|
||||
}
|
30
test/test-utils/matchers/to-have-full-hp.ts
Normal file
30
test/test-utils/matchers/to-have-full-hp.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { getPokemonNameWithAffix } from "#app/messages";
|
||||
import { isPokemonInstance, receivedStr } from "#test/test-utils/test-utils";
|
||||
import type { MatcherState, SyncExpectationResult } from "@vitest/expect";
|
||||
|
||||
/**
|
||||
* Matcher to check if a Pokemon is full hp.
|
||||
* @param received - The object to check. Should be a {@linkcode Pokemon}.
|
||||
* @returns Whether the matcher passed
|
||||
*/
|
||||
export function toHaveFullHpMatcher(this: MatcherState, received: unknown): SyncExpectationResult {
|
||||
if (!isPokemonInstance(received)) {
|
||||
return {
|
||||
pass: false,
|
||||
message: () => `Expected to receive a Pokémon, but got ${receivedStr(received)}!`,
|
||||
};
|
||||
}
|
||||
|
||||
const pass = received.isFullHp() === true;
|
||||
|
||||
const ofHpStr = `${received.getInverseHp()}/${received.getMaxHp()} HP`;
|
||||
const pkmName = getPokemonNameWithAffix(received);
|
||||
|
||||
return {
|
||||
pass,
|
||||
message: () =>
|
||||
pass
|
||||
? `Expected ${pkmName} to NOT have full hp (${ofHpStr}), but it did!`
|
||||
: `Expected ${pkmName} to have full hp, but found ${ofHpStr}.`,
|
||||
};
|
||||
}
|
32
test/test-utils/matchers/to-have-hp.ts
Normal file
32
test/test-utils/matchers/to-have-hp.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import { getPokemonNameWithAffix } from "#app/messages";
|
||||
import { isPokemonInstance, receivedStr } from "#test/test-utils/test-utils";
|
||||
import type { MatcherState, SyncExpectationResult } from "@vitest/expect";
|
||||
|
||||
/**
|
||||
* Matcher to check if a Pokemon has a specific amount of HP
|
||||
* @param received - The object to check. Should be a {@linkcode Pokemon}.
|
||||
* @param expectedHp - The expected amount of HP the {@linkcode Pokemon} has
|
||||
* @returns Whether the matcher passed
|
||||
*/
|
||||
export function toHaveHpMatcher(this: MatcherState, received: unknown, expectedHp: number): SyncExpectationResult {
|
||||
if (!isPokemonInstance(received)) {
|
||||
return {
|
||||
pass: false,
|
||||
message: () => `Expected to receive a Pokémon, but got ${receivedStr(received)}!`,
|
||||
};
|
||||
}
|
||||
|
||||
const actualHp = received.hp;
|
||||
const pass = actualHp === expectedHp;
|
||||
|
||||
const pkmName = getPokemonNameWithAffix(received);
|
||||
const maxHp = received.getMaxHp();
|
||||
|
||||
return {
|
||||
pass,
|
||||
message: () =>
|
||||
pass
|
||||
? `Expected ${pkmName} to NOT have ${expectedHp}/${maxHp} HP, but it did!`
|
||||
: `Expected ${pkmName} to have ${expectedHp}/${maxHp} HP, but found ${actualHp}/${maxHp} HP.`,
|
||||
};
|
||||
}
|
48
test/test-utils/matchers/to-have-stat-stage-matcher.ts
Normal file
48
test/test-utils/matchers/to-have-stat-stage-matcher.ts
Normal file
@ -0,0 +1,48 @@
|
||||
import { getPokemonNameWithAffix } from "#app/messages";
|
||||
import { type BattleStat, Stat } from "#enums/stat";
|
||||
import { isPokemonInstance, receivedStr } from "#test/test-utils/test-utils";
|
||||
import type { MatcherState, SyncExpectationResult } from "@vitest/expect";
|
||||
|
||||
/**
|
||||
* Matcher to check if a Pokemon has a specific {@linkcode Stat} stage
|
||||
* @param received - The object to check. Should be a {@linkcode Pokemon}.
|
||||
* @param stat - The {@linkcode Stat} to check
|
||||
* @param expectedStage - The expected numerical value of {@linkcode stat}; should be within the range `[-6, 6]`
|
||||
* @returns Whether the matcher passed
|
||||
*/
|
||||
export function toHaveStatStageMatcher(
|
||||
this: MatcherState,
|
||||
received: unknown,
|
||||
stat: BattleStat,
|
||||
expectedStage: number,
|
||||
): SyncExpectationResult {
|
||||
if (!isPokemonInstance(received)) {
|
||||
return {
|
||||
pass: false,
|
||||
message: () => `Expected to receive a Pokémon, but got ${receivedStr(received)}!`,
|
||||
};
|
||||
}
|
||||
|
||||
if (expectedStage < -6 || expectedStage > 6) {
|
||||
return {
|
||||
pass: false,
|
||||
message: () => `Expected ${expectedStage} to be within the range [-6, 6]!`,
|
||||
};
|
||||
}
|
||||
|
||||
const actualStage = received.getStatStage(stat);
|
||||
const pass = actualStage === expectedStage;
|
||||
|
||||
const pkmName = getPokemonNameWithAffix(received);
|
||||
const statName = Stat[stat];
|
||||
|
||||
return {
|
||||
pass,
|
||||
message: () =>
|
||||
pass
|
||||
? `Expected ${pkmName}'s ${statName} stat stage to NOT be ${expectedStage}, but it was!`
|
||||
: `Expected ${pkmName}'s ${statName} stage to be ${expectedStage}, but got ${actualStage}!`,
|
||||
actual: actualStage,
|
||||
expected: expectedStage,
|
||||
};
|
||||
}
|
65
test/test-utils/matchers/to-have-status-effect-matcher.ts
Normal file
65
test/test-utils/matchers/to-have-status-effect-matcher.ts
Normal file
@ -0,0 +1,65 @@
|
||||
/* biome-ignore-start lint/correctness/noUnusedImports: tsdoc imports */
|
||||
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 { 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 =
|
||||
| StatusEffect
|
||||
| { effect: StatusEffect.TOXIC; toxicTurnCount: number }
|
||||
| { effect: StatusEffect.SLEEP; sleepTurnsRemaining: number };
|
||||
|
||||
/**
|
||||
* Matcher to check if a Pokemon's {@linkcode StatusEffect} is as expected
|
||||
* @param received - The actual value received. Should be a {@linkcode Pokemon}
|
||||
* @param expectedStatus - The {@linkcode StatusEffect} the Pokemon is expected to have,
|
||||
* or a partially filled {@linkcode Status} containing the desired properties
|
||||
* @returns Whether the matcher passed
|
||||
*/
|
||||
export function toHaveStatusEffectMatcher(
|
||||
this: MatcherState,
|
||||
received: unknown,
|
||||
expectedStatus: expectedType,
|
||||
): SyncExpectationResult {
|
||||
if (!isPokemonInstance(received)) {
|
||||
return {
|
||||
pass: false,
|
||||
message: () => `Expected to receive a Pokémon, but got ${receivedStr(received)}!`,
|
||||
};
|
||||
}
|
||||
|
||||
// Convert to Status
|
||||
const expStatus: { effect: StatusEffect } & Partial<NonFunctionPropertiesRecursive<Status>> =
|
||||
typeof expectedStatus === "number"
|
||||
? {
|
||||
effect: expectedStatus,
|
||||
}
|
||||
: expectedStatus;
|
||||
|
||||
// If expected to have no status,
|
||||
if (expStatus.effect === StatusEffect.NONE) {
|
||||
k;
|
||||
}
|
||||
|
||||
const actualStatus = received.status;
|
||||
const pass = this.equals(received, expectedStatus, [
|
||||
...this.customTesters,
|
||||
this.utils.subsetEquality,
|
||||
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}!`,
|
||||
};
|
||||
}
|
49
test/test-utils/matchers/to-have-taken-damage-matcher.ts
Normal file
49
test/test-utils/matchers/to-have-taken-damage-matcher.ts
Normal file
@ -0,0 +1,49 @@
|
||||
import { getPokemonNameWithAffix } from "#app/messages";
|
||||
import { isPokemonInstance, receivedStr } from "#test/test-utils/test-utils";
|
||||
import { toDmgValue } from "#utils/common";
|
||||
import type { MatcherState, SyncExpectationResult } from "@vitest/expect";
|
||||
|
||||
//#region Types
|
||||
|
||||
export interface ToHaveTakenDamageMatcherOptions {
|
||||
/** Whether to skip the internal {@linkcode toDmgValue} call. @defaultValue false */
|
||||
skipToDmgValue?: boolean;
|
||||
} //#endregion
|
||||
|
||||
/**
|
||||
* Matcher to check if a Pokemon has taken a specific amount of damage.
|
||||
* Unless specified, will run the expected damage value through {@linkcode toDmgValue}
|
||||
* to round it down and make it a minimum of 1.
|
||||
* @param received - The object to check. Should be a {@linkcode Pokemon}.
|
||||
* @param expectedDamageTaken - The expected amount of damage the {@linkcode Pokemon} has taken
|
||||
* @param roundDown - Whether to round down {@linkcode expectedDamageTaken} with {@linkcode toDmgValue}; default `true`
|
||||
* @returns Whether the matcher passed
|
||||
*/
|
||||
export function toHaveTakenDamageMatcher(
|
||||
this: MatcherState,
|
||||
received: unknown,
|
||||
expectedDamageTaken: number,
|
||||
roundDown = true,
|
||||
): SyncExpectationResult {
|
||||
if (!isPokemonInstance(received)) {
|
||||
return {
|
||||
pass: false,
|
||||
message: () => `Expected to receive a Pokémon, but got ${receivedStr(received)}!`,
|
||||
};
|
||||
}
|
||||
|
||||
const expectedDmgValue = roundDown ? toDmgValue(expectedDamageTaken) : expectedDamageTaken;
|
||||
const actualDmgValue = received.getInverseHp();
|
||||
const pass = actualDmgValue === expectedDmgValue;
|
||||
const pkmName = getPokemonNameWithAffix(received);
|
||||
|
||||
return {
|
||||
pass,
|
||||
message: () =>
|
||||
pass
|
||||
? `Expected ${pkmName} to NOT have taken ${expectedDmgValue} damage, but it did!`
|
||||
: `Expected ${pkmName} to have taken ${expectedDmgValue} damage, but got ${actualDmgValue}!`,
|
||||
expected: expectedDmgValue,
|
||||
actual: actualDmgValue,
|
||||
};
|
||||
}
|
58
test/test-utils/matchers/to-have-terrain-matcher.ts
Normal file
58
test/test-utils/matchers/to-have-terrain-matcher.ts
Normal file
@ -0,0 +1,58 @@
|
||||
import { TerrainType } from "#app/data/terrain";
|
||||
import { isGameManagerInstance, receivedStr } from "#test/test-utils/test-utils";
|
||||
import { toReadableString } from "#utils/common";
|
||||
import type { MatcherState, SyncExpectationResult } from "@vitest/expect";
|
||||
|
||||
/**
|
||||
* Matcher to check if the {@linkcode TerrainType} is as expected
|
||||
* @param received - The object to check. Should be an instance of {@linkcode GameManager}.
|
||||
* @param expectedTerrainType - The expected {@linkcode TerrainType}, or {@linkcode TerrainType.NONE} if no terrain should be active
|
||||
* @returns Whether the matcher passed
|
||||
*/
|
||||
export function toHaveTerrainMatcher(
|
||||
this: MatcherState,
|
||||
received: unknown,
|
||||
expectedTerrainType: TerrainType,
|
||||
): SyncExpectationResult {
|
||||
if (!isGameManagerInstance(received)) {
|
||||
return {
|
||||
pass: false,
|
||||
message: () => `Expected GameManager, but got ${receivedStr(received)}!`,
|
||||
};
|
||||
}
|
||||
|
||||
if (!received.scene?.arena) {
|
||||
return {
|
||||
pass: false,
|
||||
message: () => `Expected GameManager.${received.scene ? "scene" : "scene.arena"} to be defined!`,
|
||||
};
|
||||
}
|
||||
|
||||
const actual = received.scene.arena.getTerrainType();
|
||||
const pass = actual === expectedTerrainType;
|
||||
const actualStr = toTerrainStr(actual);
|
||||
const expectedStr = toTerrainStr(expectedTerrainType);
|
||||
|
||||
return {
|
||||
pass,
|
||||
message: () =>
|
||||
pass
|
||||
? `Expected Arena to NOT have ${expectedStr} active, but it did!`
|
||||
: `Expected Arena to have ${expectedStr} active, but got ${actualStr}!`,
|
||||
actual: actualStr,
|
||||
expected: expectedStr,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a human readable string of the current {@linkcode TerrainType}.
|
||||
* @param terrainType - The {@linkcode TerrainType} to transform
|
||||
* @returns A human readable string
|
||||
*/
|
||||
function toTerrainStr(terrainType: TerrainType) {
|
||||
if (terrainType === TerrainType.NONE) {
|
||||
return "no terrain";
|
||||
}
|
||||
// TODO: Change to use updated string utils
|
||||
return toReadableString(TerrainType[terrainType] + " Terrain");
|
||||
}
|
@ -1,6 +1,9 @@
|
||||
import { getPokemonNameWithAffix } from "#app/messages";
|
||||
import { PokemonType } from "#enums/pokemon-type";
|
||||
import { Pokemon } from "#field/pokemon";
|
||||
import type { Pokemon } from "#field/pokemon";
|
||||
import { stringifyEnumArray } from "#test/test-utils/string-utils";
|
||||
import type { MatcherState, SyncExpectationResult } from "@vitest/expect";
|
||||
import { isPokemonInstance, receivedStr } from "../test-utils";
|
||||
|
||||
export interface toHaveTypesOptions {
|
||||
/**
|
||||
@ -26,39 +29,39 @@ export function toHaveTypes(
|
||||
expected: unknown,
|
||||
options: toHaveTypesOptions = {},
|
||||
): SyncExpectationResult {
|
||||
if (!(received instanceof Pokemon)) {
|
||||
if (!isPokemonInstance(received)) {
|
||||
return {
|
||||
pass: this.isNot,
|
||||
message: () => `Expected a Pokemon, but got ${this.utils.stringify(received)}!`,
|
||||
pass: false,
|
||||
message: () => `Expected to recieve a Pokémon, but got ${receivedStr(received)}!`,
|
||||
};
|
||||
}
|
||||
|
||||
if (!Array.isArray(expected) || expected.length === 0) {
|
||||
return {
|
||||
pass: this.isNot,
|
||||
message: () => `Expected to recieve an array with length >=1, but got ${this.utils.stringify(expected)}!`,
|
||||
pass: false,
|
||||
message: () => `Expected to receive an array with length >=1, but got ${this.utils.stringify(expected)}!`,
|
||||
};
|
||||
}
|
||||
|
||||
if (!expected.every((t): t is PokemonType => t in PokemonType)) {
|
||||
return {
|
||||
pass: this.isNot,
|
||||
message: () => `Expected to recieve array of PokemonTypes but got ${this.utils.stringify(expected)}!`,
|
||||
pass: false,
|
||||
message: () => `Expected to receive array of PokemonTypes but got ${this.utils.stringify(expected)}!`,
|
||||
};
|
||||
}
|
||||
|
||||
const gotSorted = pkmnTypeToStr(received.getTypes(...(options.args ?? [])));
|
||||
const wantSorted = pkmnTypeToStr(expected.slice());
|
||||
const pass = this.equals(gotSorted, wantSorted, [...this.customTesters, this.utils.iterableEquality]);
|
||||
const actualSorted = stringifyEnumArray(PokemonType, received.getTypes(...(options.args ?? [])).sort());
|
||||
const expectedSorted = stringifyEnumArray(PokemonType, expected.slice().sort());
|
||||
const matchers = options.exact
|
||||
? [...this.customTesters, this.utils.iterableEquality]
|
||||
: [...this.customTesters, this.utils.subsetEquality, this.utils.iterableEquality];
|
||||
const pass = this.equals(actualSorted, expectedSorted, matchers);
|
||||
|
||||
return {
|
||||
pass: this.isNot !== pass,
|
||||
message: () => `Expected ${received.name} to have types ${this.utils.stringify(wantSorted)}, but got ${gotSorted}!`,
|
||||
actual: gotSorted,
|
||||
expected: wantSorted,
|
||||
pass,
|
||||
message: () =>
|
||||
`Expected ${getPokemonNameWithAffix(received)} to have types ${this.utils.stringify(expectedSorted)}, but got ${actualSorted}!`,
|
||||
actual: actualSorted,
|
||||
expected: expectedSorted,
|
||||
};
|
||||
}
|
||||
|
||||
function pkmnTypeToStr(p: PokemonType[]): string[] {
|
||||
return p.sort().map(type => PokemonType[type]);
|
||||
}
|
||||
|
64
test/test-utils/matchers/to-have-used-move-matcher.ts
Normal file
64
test/test-utils/matchers/to-have-used-move-matcher.ts
Normal file
@ -0,0 +1,64 @@
|
||||
import { getPokemonNameWithAffix } from "#app/messages";
|
||||
import type { MoveId } from "#enums/move-id";
|
||||
// biome-ignore lint/correctness/noUnusedImports: TSDocs
|
||||
import type { Pokemon } from "#field/pokemon";
|
||||
import { getOrdinal } from "#test/test-utils/string-utils";
|
||||
import { isPokemonInstance, receivedStr } from "#test/test-utils/test-utils";
|
||||
import type { TurnMove } from "#types/turn-move";
|
||||
import type { MatcherState, SyncExpectationResult } from "@vitest/expect";
|
||||
|
||||
/**
|
||||
* Matcher to check if a {@linkcode Pokemon} has used a specific {@linkcode MoveId} at the given .
|
||||
* @param received - The actual value received. Should be a {@linkcode Pokemon}
|
||||
* @param expectedValue - The expected value; can be a {@linkcode MoveId} or a partially filled {@linkcode TurnMove}
|
||||
* @param index - The index of the move history entry to check, in order from most recent to least recent.
|
||||
* Default `0` (last used move)
|
||||
* @returns Whether the matcher passed
|
||||
*/
|
||||
export function toHaveUsedMoveMatcher(
|
||||
this: MatcherState,
|
||||
received: unknown,
|
||||
expectedResult: MoveId | Partial<TurnMove>,
|
||||
index = 0,
|
||||
): SyncExpectationResult {
|
||||
if (!isPokemonInstance(received)) {
|
||||
return {
|
||||
pass: false,
|
||||
message: () => `Expected to receive a Pokémon, but got ${receivedStr(received)}!`,
|
||||
};
|
||||
}
|
||||
|
||||
const move: TurnMove | undefined = received.getLastXMoves(-1)[index];
|
||||
const pkmName = getPokemonNameWithAffix(received);
|
||||
|
||||
if (move === undefined) {
|
||||
return {
|
||||
pass: false,
|
||||
message: () => `Expected ${pkmName} to have used ${index + 1} moves, but it didn't!`,
|
||||
actual: received.getLastXMoves(-1),
|
||||
};
|
||||
}
|
||||
|
||||
// Coerce to a `TurnMove`
|
||||
if (typeof expectedResult === "number") {
|
||||
expectedResult = { move: expectedResult };
|
||||
}
|
||||
|
||||
const moveIndexStr = index === 0 ? "last move" : `${getOrdinal(index)} most recent move`;
|
||||
|
||||
const pass = this.equals(move, expectedResult, [
|
||||
...this.customTesters,
|
||||
this.utils.subsetEquality,
|
||||
this.utils.iterableEquality,
|
||||
]);
|
||||
|
||||
return {
|
||||
pass,
|
||||
message: () =>
|
||||
pass
|
||||
? `Expected ${pkmName}'s ${moveIndexStr} NOT to match ${this.utils.stringify(expectedResult)}, but it did!`
|
||||
: `Expected ${pkmName}'s ${moveIndexStr} to match ${this.utils.stringify(expectedResult)}, but got ${this.utils.stringify(move)}!`,
|
||||
expected: expectedResult,
|
||||
actual: move,
|
||||
};
|
||||
}
|
59
test/test-utils/matchers/to-have-weather-matcher.ts
Normal file
59
test/test-utils/matchers/to-have-weather-matcher.ts
Normal file
@ -0,0 +1,59 @@
|
||||
import { WeatherType } from "#enums/weather-type";
|
||||
import { isGameManagerInstance, receivedStr } from "#test/test-utils/test-utils";
|
||||
import { toReadableString } from "#utils/common";
|
||||
import type { MatcherState, SyncExpectationResult } from "@vitest/expect";
|
||||
|
||||
/**
|
||||
* Matcher to check if the {@linkcode WeatherType} is as expected
|
||||
* @param received - The object to check. Expects an instance of {@linkcode GameManager}.
|
||||
* @param expectedWeatherType - The expected {@linkcode WeatherType}
|
||||
* @returns Whether the matcher passed
|
||||
*/
|
||||
export function toHaveWeatherMatcher(
|
||||
this: MatcherState,
|
||||
received: unknown,
|
||||
expectedWeatherType: WeatherType,
|
||||
): SyncExpectationResult {
|
||||
if (!isGameManagerInstance(received)) {
|
||||
return {
|
||||
pass: false,
|
||||
message: () => `Expected GameManager, but got ${receivedStr(received)}!`,
|
||||
};
|
||||
}
|
||||
|
||||
if (!received.scene?.arena) {
|
||||
return {
|
||||
pass: false,
|
||||
message: () => `Expected GameManager.${received.scene ? "scene" : "scene.arena"} to be defined!`,
|
||||
};
|
||||
}
|
||||
|
||||
const actual = received.scene.arena.getWeatherType();
|
||||
const pass = actual === expectedWeatherType;
|
||||
const actualStr = toWeatherStr(actual);
|
||||
const expectedStr = toWeatherStr(expectedWeatherType);
|
||||
|
||||
return {
|
||||
pass,
|
||||
message: () =>
|
||||
pass
|
||||
? `Expected Arena to NOT have ${expectedStr} weather active, but it did!`
|
||||
: `Expected Arena to have ${expectedStr} weather active, but got ${actualStr}!`,
|
||||
actual: actualStr,
|
||||
expected: expectedStr,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a human readable representation of the current {@linkcode WeatherType}.
|
||||
* @param weatherType - The {@linkcode WeatherType} to transform
|
||||
* @returns A human readable string
|
||||
*/
|
||||
function toWeatherStr(weatherType: WeatherType) {
|
||||
if (weatherType === WeatherType.NONE) {
|
||||
return "no weather";
|
||||
}
|
||||
|
||||
// TODO: Change to use updated string utils
|
||||
return toReadableString(WeatherType[weatherType]);
|
||||
}
|
129
test/test-utils/string-utils.ts
Normal file
129
test/test-utils/string-utils.ts
Normal file
@ -0,0 +1,129 @@
|
||||
import type { EnumOrObject, EnumValues, NormalEnum, TSNumericEnum } from "#types/enum-types";
|
||||
import { toReadableString } from "#utils/common";
|
||||
import { enumValueToKey } from "#utils/enums";
|
||||
|
||||
type Casing = "Preserve" | "Title";
|
||||
|
||||
interface getEnumStrOptions {
|
||||
/**
|
||||
* A string denoting the casing method to use.
|
||||
* @defaultValue "Preserve"
|
||||
*/
|
||||
casing?: Casing;
|
||||
/**
|
||||
* If present, will be added to the beginning of the enum string.
|
||||
*/
|
||||
prefix?: string;
|
||||
/**
|
||||
* If present, will be added to the end of the enum string.
|
||||
*/
|
||||
suffix?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to return the name of an enum member or const object value, alongside its corresponding value.
|
||||
* @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 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
|
||||
* ```ts
|
||||
* enum fakeEnum {
|
||||
* ONE: 1,
|
||||
* TWO: 2,
|
||||
* THREE: 3,
|
||||
* }
|
||||
* 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<E extends EnumOrObject>(
|
||||
obj: E,
|
||||
val: EnumValues<E>,
|
||||
{ casing = "Preserve", prefix = "", suffix = "" }: getEnumStrOptions = {},
|
||||
): string {
|
||||
let casingFunc: ((s: string) => string) | undefined;
|
||||
switch (casing) {
|
||||
case "Preserve":
|
||||
break;
|
||||
case "Title":
|
||||
casingFunc = toReadableString;
|
||||
break;
|
||||
}
|
||||
|
||||
let stringPart =
|
||||
obj[val] !== undefined
|
||||
? // TS reverse mapped enum
|
||||
(obj[val] as string)
|
||||
: // Normal enum/`const object`
|
||||
(enumValueToKey(obj as NormalEnum<E>, val) as string);
|
||||
|
||||
if (casingFunc) {
|
||||
stringPart = casingFunc(stringPart);
|
||||
}
|
||||
|
||||
return `${prefix}${stringPart}${suffix} (=${val})`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an array of enums or `const object`s into a readable string version.
|
||||
* @param obj - The {@linkcode EnumOrObject} to source reverse mappings from
|
||||
* @param enums - An array of {@linkcode obj}'s values
|
||||
* @returns The stringified representation of `enums`
|
||||
* @example
|
||||
* ```ts
|
||||
* enum fakeEnum {
|
||||
* ONE: 1,
|
||||
* TWO: 2,
|
||||
* THREE: 3,
|
||||
* }
|
||||
* console.log(stringifyEnumArray(fakeEnum, [fakeEnum.ONE, fakeEnum.TWO, fakeEnum.THREE])); // Output: "[ONE, TWO, THREE] (=[1, 2, 3])"
|
||||
* ```
|
||||
*/
|
||||
export function stringifyEnumArray<E extends EnumOrObject>(obj: E, enums: E[keyof E][]): string {
|
||||
if (obj.length === 0) {
|
||||
return "[]";
|
||||
}
|
||||
|
||||
const vals = enums.slice();
|
||||
let names: string[];
|
||||
|
||||
if (obj[enums[0]] !== undefined) {
|
||||
// Reverse mapping exists - `obj` is a `TSNumericEnum` and its reverse mapped counterparts
|
||||
names = enums.map(e => (obj as TSNumericEnum<E>)[e] as string);
|
||||
} else {
|
||||
// No reverse mapping exists means `obj` is a `NormalEnum`
|
||||
names = enums.map(e => enumValueToKey(obj as NormalEnum<E>, e) as string);
|
||||
}
|
||||
|
||||
return `[${names.join(", ")}] (=[${vals.join(", ")}])`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a number into an English ordinal.
|
||||
* @param num - The number to convert into an ordinal
|
||||
* @returns The ordinal representation of {@linkcode num}.
|
||||
* @example
|
||||
* ```ts
|
||||
* console.log(getOrdinal(1)); // Output: "1st"
|
||||
* console.log(getOrdinal(12)); // Output: "12th"
|
||||
* console.log(getOrdinal(24)); // Output: "24th"
|
||||
* ```
|
||||
*/
|
||||
export function getOrdinal(num: number): string {
|
||||
const tens = num % 10;
|
||||
const hundreds = num % 100;
|
||||
if (tens === 1 && hundreds !== 11) {
|
||||
return num + "st";
|
||||
}
|
||||
if (tens === 2 && hundreds !== 12) {
|
||||
return num + "nd";
|
||||
}
|
||||
if (tens === 3 && hundreds !== 13) {
|
||||
return num + "rd";
|
||||
}
|
||||
return num + "th";
|
||||
}
|
@ -1,3 +1,5 @@
|
||||
import { Pokemon } from "#field/pokemon";
|
||||
import type { GameManager } from "#test/test-utils/game-manager";
|
||||
import i18next, { type ParseKeys } from "i18next";
|
||||
import { vi } from "vitest";
|
||||
|
||||
@ -29,3 +31,54 @@ export function arrayOfRange(start: number, end: number) {
|
||||
export function getApiBaseUrl() {
|
||||
return import.meta.env.VITE_SERVER_URL ?? "http://localhost:8001";
|
||||
}
|
||||
|
||||
type TypeOfResult = "undefined" | "object" | "boolean" | "number" | "bigint" | "string" | "symbol" | "function";
|
||||
|
||||
/**
|
||||
* Helper to determine the actual type of the received object as human readable string
|
||||
* @param received - The received object
|
||||
* @returns A human readable string of the received object (type)
|
||||
*/
|
||||
export function receivedStr(received: unknown, expectedType: TypeOfResult = "object"): string {
|
||||
if (received === null) {
|
||||
return "null";
|
||||
}
|
||||
if (received === undefined) {
|
||||
return "undefined";
|
||||
}
|
||||
if (typeof received !== expectedType) {
|
||||
return typeof received;
|
||||
}
|
||||
if (expectedType === "object") {
|
||||
return received.constructor.name;
|
||||
}
|
||||
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to check if the received object is an {@linkcode object}
|
||||
* @param received - The object to check
|
||||
* @returns Whether the object is an {@linkcode object}.
|
||||
*/
|
||||
function isObject(received: unknown): received is object {
|
||||
return received !== null && typeof received === "object";
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to check if a given object is a {@linkcode Pokemon}.
|
||||
* @param received - The object to check
|
||||
* @return Whether `received` is a {@linkcode Pokemon} instance.
|
||||
*/
|
||||
export function isPokemonInstance(received: unknown): received is Pokemon {
|
||||
return isObject(received) && received instanceof Pokemon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an object is a {@linkcode GameManager} instance
|
||||
* @param received - The object to check
|
||||
* @returns Whether the object is a {@linkcode GameManager} instance.
|
||||
*/
|
||||
export function isGameManagerInstance(received: unknown): received is GameManager {
|
||||
return isObject(received) && (received as GameManager).constructor.name === "GameManager";
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user