mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-08-14 03:19:28 +02:00
Merge 6a2c92dad7
into 2e3146912a
This commit is contained in:
commit
0d4ea50edd
@ -1,8 +1,8 @@
|
|||||||
import { AbilityId } from "#enums/ability-id";
|
import { AbilityId } from "#enums/ability-id";
|
||||||
|
import { getHeldItemCategory, HeldItemCategoryId } from "#enums/held-item-id";
|
||||||
import { MoveId } from "#enums/move-id";
|
import { MoveId } from "#enums/move-id";
|
||||||
import { RewardId } from "#enums/reward-id";
|
import { RewardId } from "#enums/reward-id";
|
||||||
import { SpeciesId } from "#enums/species-id";
|
import { SpeciesId } from "#enums/species-id";
|
||||||
import { BerryHeldItem } from "#items/berry";
|
|
||||||
import { HeldItemReward } from "#items/reward";
|
import { HeldItemReward } from "#items/reward";
|
||||||
import { GameManager } from "#test/test-utils/game-manager";
|
import { GameManager } from "#test/test-utils/game-manager";
|
||||||
import { generateRewardForTest } from "#test/test-utils/reward-test-utils";
|
import { generateRewardForTest } from "#test/test-utils/reward-test-utils";
|
||||||
@ -39,9 +39,13 @@ describe("{{description}}", () => {
|
|||||||
it("should do XYZ when applied", async () => {
|
it("should do XYZ when applied", async () => {
|
||||||
await game.classicMode.startBattle([SpeciesId.FEEBAS]);
|
await game.classicMode.startBattle([SpeciesId.FEEBAS]);
|
||||||
|
|
||||||
const reward = generateRewardForTest(RewardId.BERRY);
|
const feebas = game.field.getPlayerPokemon();
|
||||||
expect(reward).toBeInstanceOf(HeldItemReward);
|
|
||||||
game.scene.applyReward(reward, []);
|
const reward = generateRewardForTest(RewardId.BERRY)!;
|
||||||
expect(true).toBe(true);
|
expect(reward).toBeInstanceOf(HeldItemReward); // Replace with actual reward instance
|
||||||
|
expect(getHeldItemCategory(reward["itemId"])).toBe(HeldItemCategoryId.BERRY);
|
||||||
|
game.scene.applyReward(reward, { pokemon: feebas });
|
||||||
|
|
||||||
|
expect(feebas).toHaveHeldItem(HeldItemCategoryId.BERRY);
|
||||||
});
|
});
|
||||||
});
|
});
|
@ -101,8 +101,8 @@ async function promptFileName(selectedType) {
|
|||||||
*/
|
*/
|
||||||
function getBoilerplatePath(choiceType) {
|
function getBoilerplatePath(choiceType) {
|
||||||
switch (choiceType) {
|
switch (choiceType) {
|
||||||
// case "Reward":
|
case "Reward":
|
||||||
// return path.join(__dirname, "boilerplates/reward.ts");
|
return path.join(__dirname, "boilerplates/rewards/reward.ts");
|
||||||
default:
|
default:
|
||||||
return path.join(__dirname, "boilerplates/default.ts");
|
return path.join(__dirname, "boilerplates/default.ts");
|
||||||
}
|
}
|
||||||
|
@ -275,7 +275,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
|
|
||||||
private shinySparkle: Phaser.GameObjects.Sprite;
|
private shinySparkle: Phaser.GameObjects.Sprite;
|
||||||
|
|
||||||
public heldItemManager: HeldItemManager;
|
public readonly heldItemManager: HeldItemManager = new HeldItemManager();
|
||||||
|
|
||||||
// TODO: Rework this eventually
|
// TODO: Rework this eventually
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -185,6 +185,7 @@ type ApplyHeldItemsParams = {
|
|||||||
export function applyHeldItems<T extends HeldItemEffect>(effect: T, params: ApplyHeldItemsParams[T]) {
|
export function applyHeldItems<T extends HeldItemEffect>(effect: T, params: ApplyHeldItemsParams[T]) {
|
||||||
const pokemon = params.pokemon;
|
const pokemon = params.pokemon;
|
||||||
if (pokemon) {
|
if (pokemon) {
|
||||||
|
// TODO: Make this use `getHeldItems` and make `heldItems` array private
|
||||||
for (const item of Object.keys(pokemon.heldItemManager.heldItems)) {
|
for (const item of Object.keys(pokemon.heldItemManager.heldItems)) {
|
||||||
if (allHeldItems[item].effects.includes(effect)) {
|
if (allHeldItems[item].effects.includes(effect)) {
|
||||||
allHeldItems[item].apply(params);
|
allHeldItems[item].apply(params);
|
||||||
|
@ -32,7 +32,7 @@ export function isHeldItemSpecs(entry: any): entry is HeldItemSpecs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Types used for form change items
|
// Types used for form change items
|
||||||
interface FormChangeItemData {
|
export interface FormChangeItemData {
|
||||||
active: boolean;
|
active: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ import {
|
|||||||
isItemInRequested,
|
isItemInRequested,
|
||||||
} from "#enums/held-item-id";
|
} from "#enums/held-item-id";
|
||||||
import {
|
import {
|
||||||
|
type FormChangeItemData,
|
||||||
type FormChangeItemPropertyMap,
|
type FormChangeItemPropertyMap,
|
||||||
type FormChangeItemSpecs,
|
type FormChangeItemSpecs,
|
||||||
type HeldItemConfiguration,
|
type HeldItemConfiguration,
|
||||||
@ -16,9 +17,10 @@ import {
|
|||||||
type HeldItemSpecs,
|
type HeldItemSpecs,
|
||||||
isHeldItemSpecs,
|
isHeldItemSpecs,
|
||||||
} from "#items/held-item-data-types";
|
} from "#items/held-item-data-types";
|
||||||
import { getTypedEntries, getTypedKeys } from "#utils/common";
|
import { getTypedKeys } from "#utils/common";
|
||||||
|
|
||||||
export class HeldItemManager {
|
export class HeldItemManager {
|
||||||
|
// TODO: There should be a way of making these private...
|
||||||
public heldItems: HeldItemDataMap;
|
public heldItems: HeldItemDataMap;
|
||||||
public formChangeItems: FormChangeItemPropertyMap;
|
public formChangeItems: FormChangeItemPropertyMap;
|
||||||
|
|
||||||
@ -41,13 +43,14 @@ export class HeldItemManager {
|
|||||||
|
|
||||||
generateHeldItemConfiguration(restrictedIds?: HeldItemId[]): HeldItemConfiguration {
|
generateHeldItemConfiguration(restrictedIds?: HeldItemId[]): HeldItemConfiguration {
|
||||||
const config: HeldItemConfiguration = [];
|
const config: HeldItemConfiguration = [];
|
||||||
for (const [id, item] of getTypedEntries(this.heldItems)) {
|
for (const [id, item] of this.getHeldItemEntries()) {
|
||||||
|
// TODO: `in` breaks with arrays
|
||||||
if (item && (!restrictedIds || id in restrictedIds)) {
|
if (item && (!restrictedIds || id in restrictedIds)) {
|
||||||
const specs: HeldItemSpecs = { ...item, id };
|
const specs: HeldItemSpecs = { ...item, id };
|
||||||
config.push({ entry: specs, count: 1 });
|
config.push({ entry: specs, count: 1 });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const [id, item] of getTypedEntries(this.formChangeItems)) {
|
for (const [id, item] of this.getFormChangeItemEntries()) {
|
||||||
if (item) {
|
if (item) {
|
||||||
const specs: FormChangeItemSpecs = { ...item, id };
|
const specs: FormChangeItemSpecs = { ...item, id };
|
||||||
config.push({ entry: specs, count: 1 });
|
config.push({ entry: specs, count: 1 });
|
||||||
@ -58,13 +61,13 @@ export class HeldItemManager {
|
|||||||
|
|
||||||
generateSaveData(): HeldItemSaveData {
|
generateSaveData(): HeldItemSaveData {
|
||||||
const saveData: HeldItemSaveData = [];
|
const saveData: HeldItemSaveData = [];
|
||||||
for (const [id, item] of getTypedEntries(this.heldItems)) {
|
for (const [id, item] of this.getHeldItemEntries()) {
|
||||||
if (item) {
|
if (item) {
|
||||||
const specs: HeldItemSpecs = { ...item, id };
|
const specs: HeldItemSpecs = { ...item, id };
|
||||||
saveData.push(specs);
|
saveData.push(specs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const [id, item] of getTypedEntries(this.formChangeItems)) {
|
for (const [id, item] of this.getFormChangeItemEntries()) {
|
||||||
if (item) {
|
if (item) {
|
||||||
const specs: FormChangeItemSpecs = { ...item, id };
|
const specs: FormChangeItemSpecs = { ...item, id };
|
||||||
saveData.push(specs);
|
saveData.push(specs);
|
||||||
@ -77,28 +80,32 @@ export class HeldItemManager {
|
|||||||
return getTypedKeys(this.heldItems);
|
return getTypedKeys(this.heldItems);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getHeldItemEntries(): [HeldItemId, HeldItemSpecs][] {
|
||||||
|
return Object.entries(this.heldItems) as unknown as [HeldItemId, HeldItemSpecs][];
|
||||||
|
}
|
||||||
|
|
||||||
getTransferableHeldItems(): HeldItemId[] {
|
getTransferableHeldItems(): HeldItemId[] {
|
||||||
return getTypedKeys(this.heldItems).filter(k => allHeldItems[k].isTransferable);
|
return this.getHeldItems().filter(k => allHeldItems[k].isTransferable);
|
||||||
}
|
}
|
||||||
|
|
||||||
getStealableHeldItems(): HeldItemId[] {
|
getStealableHeldItems(): HeldItemId[] {
|
||||||
return getTypedKeys(this.heldItems).filter(k => allHeldItems[k].isStealable);
|
return this.getHeldItems().filter(k => allHeldItems[k].isStealable);
|
||||||
}
|
}
|
||||||
|
|
||||||
getSuppressableHeldItems(): HeldItemId[] {
|
getSuppressableHeldItems(): HeldItemId[] {
|
||||||
return getTypedKeys(this.heldItems).filter(k => allHeldItems[k].isSuppressable);
|
return this.getHeldItems().filter(k => allHeldItems[k].isSuppressable);
|
||||||
}
|
}
|
||||||
|
|
||||||
hasItem(itemType: HeldItemId | HeldItemCategoryId): boolean {
|
hasItem(itemType: HeldItemId | HeldItemCategoryId): boolean {
|
||||||
if (isCategoryId(itemType)) {
|
if (isCategoryId(itemType)) {
|
||||||
return getTypedKeys(this.heldItems).some(id => isItemInCategory(id, itemType as HeldItemCategoryId));
|
return this.getHeldItems().some(id => isItemInCategory(id, itemType as HeldItemCategoryId));
|
||||||
}
|
}
|
||||||
return itemType in this.heldItems;
|
return itemType in this.heldItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
hasTransferableItem(itemType: HeldItemId | HeldItemCategoryId): boolean {
|
hasTransferableItem(itemType: HeldItemId | HeldItemCategoryId): boolean {
|
||||||
if (isCategoryId(itemType)) {
|
if (isCategoryId(itemType)) {
|
||||||
return getTypedKeys(this.heldItems).some(
|
return this.getHeldItems().some(
|
||||||
id => isItemInCategory(id, itemType as HeldItemCategoryId) && allHeldItems[id].isTransferable,
|
id => isItemInCategory(id, itemType as HeldItemCategoryId) && allHeldItems[id].isTransferable,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -128,7 +135,7 @@ export class HeldItemManager {
|
|||||||
overrideItems(newItems: HeldItemDataMap) {
|
overrideItems(newItems: HeldItemDataMap) {
|
||||||
this.heldItems = newItems;
|
this.heldItems = newItems;
|
||||||
// The following is to allow randomly generated item configs to have stack 0
|
// The following is to allow randomly generated item configs to have stack 0
|
||||||
for (const [item, properties] of getTypedEntries(this.heldItems)) {
|
for (const [item, properties] of this.getHeldItemEntries()) {
|
||||||
if (!properties || properties.stack <= 0) {
|
if (!properties || properties.stack <= 0) {
|
||||||
delete this.heldItems[item];
|
delete this.heldItems[item];
|
||||||
}
|
}
|
||||||
@ -176,6 +183,7 @@ export class HeldItemManager {
|
|||||||
item.stack -= removeStack;
|
item.stack -= removeStack;
|
||||||
|
|
||||||
if (all || item.stack <= 0) {
|
if (all || item.stack <= 0) {
|
||||||
|
// TODO: Delete is bad for performance
|
||||||
delete this.heldItems[itemType];
|
delete this.heldItems[itemType];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -219,9 +227,14 @@ export class HeldItemManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getFormChangeItems(): FormChangeItem[] {
|
getFormChangeItems(): FormChangeItem[] {
|
||||||
|
// TODO: Please stop using `map(k => k)`
|
||||||
return getTypedKeys(this.formChangeItems).map(k => k);
|
return getTypedKeys(this.formChangeItems).map(k => k);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getFormChangeItemEntries(): [FormChangeItem, FormChangeItemData | undefined][] {
|
||||||
|
return Object.entries(this.formChangeItems) as unknown as [FormChangeItem, FormChangeItemData | undefined][];
|
||||||
|
}
|
||||||
|
|
||||||
getActiveFormChangeItems(): FormChangeItem[] {
|
getActiveFormChangeItems(): FormChangeItem[] {
|
||||||
return this.getFormChangeItems().filter(m => this.formChangeItems[m]?.active);
|
return this.getFormChangeItems().filter(m => this.formChangeItems[m]?.active);
|
||||||
}
|
}
|
||||||
|
@ -267,6 +267,7 @@ export function formatStat(stat: number, forHp = false): string {
|
|||||||
return formatLargeNumber(stat, forHp ? 100_000 : 1_000_000);
|
return formatLargeNumber(stat, forHp ? 100_000 : 1_000_000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Remove in place of enum utils
|
||||||
export function getTypedKeys<T extends Record<number, any>, K extends number = Extract<keyof T, number>>(obj: T): K[] {
|
export function getTypedKeys<T extends Record<number, any>, K extends number = Extract<keyof T, number>>(obj: T): K[] {
|
||||||
return Object.keys(obj).map(k => Number(k) as K);
|
return Object.keys(obj).map(k => Number(k) as K);
|
||||||
}
|
}
|
||||||
|
27
test/@types/test-helpers.ts
Normal file
27
test/@types/test-helpers.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import type { AtLeastOne, NonFunctionPropertiesRecursive as nonFunc } from "#types/type-helpers";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper type to admit an object containing the given properties
|
||||||
|
* _and at least 1 other non-function property_.
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* type foo = {
|
||||||
|
* qux: 1 | 2 | 3,
|
||||||
|
* bar: number,
|
||||||
|
* baz: string
|
||||||
|
* quux: () => void; // ignored!
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* type quxAndSomethingElse = OneOther<foo, "qux">
|
||||||
|
*
|
||||||
|
* const good1: quxAndSomethingElse = {qux: 1, bar: 3} // OK!
|
||||||
|
* const good2: quxAndSomethingElse = {qux: 2, baz: "4", bar: 12} // OK!
|
||||||
|
* const bad1: quxAndSomethingElse = {baz: "4", bar: 12} // Errors because `qux` is required
|
||||||
|
* const bad2: quxAndSomethingElse = {qux: 1} // Errors because at least 1 thing _other_ than `qux` is required
|
||||||
|
* ```
|
||||||
|
* @typeParam O - The object to source keys from
|
||||||
|
* @typeParam K - One or more of O's keys to render mandatory
|
||||||
|
*/
|
||||||
|
export type OneOther<O extends object, K extends keyof O> = AtLeastOne<Omit<nonFunc<O>, K>> & {
|
||||||
|
[key in K]: O[K];
|
||||||
|
};
|
9
test/@types/vitest.d.ts
vendored
9
test/@types/vitest.d.ts
vendored
@ -15,6 +15,7 @@ import type { AtLeastOne } from "#types/type-helpers";
|
|||||||
import type { expect } from "vitest";
|
import type { expect } from "vitest";
|
||||||
import type Overrides from "#app/overrides";
|
import type Overrides from "#app/overrides";
|
||||||
import type { PokemonMove } from "#moves/pokemon-move";
|
import type { PokemonMove } from "#moves/pokemon-move";
|
||||||
|
import { expectedHeldItemType } from "#test/test-utils/matchers/to-have-held-item";
|
||||||
|
|
||||||
declare module "vitest" {
|
declare module "vitest" {
|
||||||
interface Assertion {
|
interface Assertion {
|
||||||
@ -135,5 +136,13 @@ declare module "vitest" {
|
|||||||
* or contains the desired move more than once, this will fail the test.
|
* or contains the desired move more than once, this will fail the test.
|
||||||
*/
|
*/
|
||||||
toHaveUsedPP(expectedMove: MoveId, ppUsed: number | "all"): void;
|
toHaveUsedPP(expectedMove: MoveId, ppUsed: number | "all"): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether a {@linkcode Pokemon} has a given held item.
|
||||||
|
* @param received - The object to check. Should be a {@linkcode Pokemon}.
|
||||||
|
* @param expectedItem - A {@linkcode HeldItemId} or {@linkcode HeldItemCategoryId} to check, or a partially filled
|
||||||
|
* {@linkcode HeldItemSpecs} containing the desired values
|
||||||
|
*/
|
||||||
|
toHaveHeldItem(expected: expectedHeldItemType): void;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import { toHaveBattlerTag } from "#test/test-utils/matchers/to-have-battler-tag"
|
|||||||
import { toHaveEffectiveStat } from "#test/test-utils/matchers/to-have-effective-stat";
|
import { toHaveEffectiveStat } from "#test/test-utils/matchers/to-have-effective-stat";
|
||||||
import { toHaveFainted } from "#test/test-utils/matchers/to-have-fainted";
|
import { toHaveFainted } from "#test/test-utils/matchers/to-have-fainted";
|
||||||
import { toHaveFullHp } from "#test/test-utils/matchers/to-have-full-hp";
|
import { toHaveFullHp } from "#test/test-utils/matchers/to-have-full-hp";
|
||||||
|
import { toHaveHeldItem } from "#test/test-utils/matchers/to-have-held-item";
|
||||||
import { toHaveHp } from "#test/test-utils/matchers/to-have-hp";
|
import { toHaveHp } from "#test/test-utils/matchers/to-have-hp";
|
||||||
import { toHaveStatStage } from "#test/test-utils/matchers/to-have-stat-stage";
|
import { toHaveStatStage } from "#test/test-utils/matchers/to-have-stat-stage";
|
||||||
import { toHaveStatusEffect } from "#test/test-utils/matchers/to-have-status-effect";
|
import { toHaveStatusEffect } from "#test/test-utils/matchers/to-have-status-effect";
|
||||||
@ -36,4 +37,5 @@ expect.extend({
|
|||||||
toHaveHp,
|
toHaveHp,
|
||||||
toHaveFainted,
|
toHaveFainted,
|
||||||
toHaveUsedPP,
|
toHaveUsedPP,
|
||||||
|
toHaveHeldItem,
|
||||||
});
|
});
|
||||||
|
103
test/test-utils/matchers/to-have-held-item.ts
Normal file
103
test/test-utils/matchers/to-have-held-item.ts
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
import { getPokemonNameWithAffix } from "#app/messages";
|
||||||
|
import { HeldItemCategoryId, HeldItemId, isCategoryId } from "#enums/held-item-id";
|
||||||
|
// biome-ignore lint/correctness/noUnusedImports: TSDoc
|
||||||
|
import type { Pokemon } from "#field/pokemon";
|
||||||
|
import type { HeldItemSpecs } from "#items/held-item-data-types";
|
||||||
|
import type { OneOther } from "#test/@types/test-helpers";
|
||||||
|
import { getOnelineDiffStr, stringifyEnumArray } from "#test/test-utils/string-utils";
|
||||||
|
import { isPokemonInstance, receivedStr } from "#test/test-utils/test-utils";
|
||||||
|
import { enumValueToKey } from "#utils/enums";
|
||||||
|
import type { MatcherState, SyncExpectationResult } from "@vitest/expect";
|
||||||
|
|
||||||
|
export type expectedHeldItemType = HeldItemId | HeldItemCategoryId | OneOther<HeldItemSpecs, "id" | "stack">;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Matcher that checks if a {@linkcode Pokemon} has a given held item.
|
||||||
|
* @param received - The object to check. Should be a {@linkcode Pokemon}.
|
||||||
|
* @param expectedItem - A {@linkcode HeldItemId} or {@linkcode HeldItemCategoryId} to check, or a partially filled
|
||||||
|
* {@linkcode HeldItemSpecs} containing the desired values
|
||||||
|
* @returns Whether the matcher passed
|
||||||
|
*/
|
||||||
|
export function toHaveHeldItem(
|
||||||
|
this: MatcherState,
|
||||||
|
received: unknown,
|
||||||
|
// Simplified typing; full one is in overloads
|
||||||
|
expectedItem: HeldItemId | HeldItemCategoryId | (Partial<HeldItemSpecs> & { id: HeldItemId }),
|
||||||
|
): SyncExpectationResult {
|
||||||
|
if (!isPokemonInstance(received)) {
|
||||||
|
return {
|
||||||
|
pass: this.isNot,
|
||||||
|
message: () => `Expected to receive a Pokémon, but got ${receivedStr(received)}!`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const pkmName = getPokemonNameWithAffix(received);
|
||||||
|
|
||||||
|
// If a category was requested OR we lack the item in question, show an error message.
|
||||||
|
if (typeof expectedItem === "number" || !received.heldItemManager.hasItem(expectedItem.id)) {
|
||||||
|
expectedItem = typeof expectedItem === "number" ? expectedItem : expectedItem.id;
|
||||||
|
|
||||||
|
const pass = received.heldItemManager.hasItem(expectedItem);
|
||||||
|
|
||||||
|
const actualStr = stringifyEnumArray(HeldItemId, received.heldItemManager.getHeldItems(), toHexStr);
|
||||||
|
const expectedStr = itemIdToString(expectedItem);
|
||||||
|
|
||||||
|
return {
|
||||||
|
pass,
|
||||||
|
// "Expected Magikarp to have an item with category HeldItemCategory.BERRY (=0xADAD), but it didn't!"
|
||||||
|
message: () =>
|
||||||
|
pass
|
||||||
|
? `Expected ${pkmName} to NOT have an item with ${expectedStr}, but it did!`
|
||||||
|
: `Expected ${pkmName} to have an item with ${expectedStr}, but it didn't!`,
|
||||||
|
expected: expectedStr,
|
||||||
|
actual: actualStr,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the properties of the requested held item
|
||||||
|
const items = Object.values(received.heldItemManager["heldItems"]);
|
||||||
|
const pass = items.some(d =>
|
||||||
|
this.equals(d, expectedItem, [...this.customTesters, this.utils.subsetEquality, this.utils.iterableEquality]),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Convert item IDs in the diff into actual numbers
|
||||||
|
const expectedReadable = {
|
||||||
|
...expectedItem,
|
||||||
|
id: toHexStr(expectedItem.id),
|
||||||
|
};
|
||||||
|
const actualReadable = received.heldItemManager["getHeldItemEntries"]().map(([id, spec]) => ({
|
||||||
|
...spec,
|
||||||
|
id: toHexStr(id),
|
||||||
|
}));
|
||||||
|
const expectedStr = getOnelineDiffStr.call(this, expectedReadable);
|
||||||
|
|
||||||
|
return {
|
||||||
|
pass,
|
||||||
|
message: () =>
|
||||||
|
pass
|
||||||
|
? `Expected ${pkmName} to NOT have an item matching ${expectedStr}, but it did!`
|
||||||
|
: `Expected ${pkmName} to have an item matching ${expectedStr}, but it didn't!`,
|
||||||
|
expected: expectedReadable,
|
||||||
|
actual: actualReadable,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const PADDING = 4;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a number into a readable hexadecimal format.
|
||||||
|
* @param num - The number to convert
|
||||||
|
* @returns The hex string
|
||||||
|
*/
|
||||||
|
function toHexStr(num: number): string {
|
||||||
|
return `0x${num.toString(16).padStart(PADDING, "0")}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function itemIdToString(id: HeldItemId | HeldItemCategoryId): string {
|
||||||
|
if (isCategoryId(id)) {
|
||||||
|
const catStr = enumValueToKey(HeldItemCategoryId, id);
|
||||||
|
return `catgeory HeldItemCategory.${catStr} (=${toHexStr(id)})`;
|
||||||
|
}
|
||||||
|
const idStr = enumValueToKey(HeldItemId, id);
|
||||||
|
return `ID HeldItemId.${idStr} (=${toHexStr(id)})`;
|
||||||
|
}
|
@ -73,8 +73,9 @@ export function getEnumStr<E extends EnumOrObject>(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert an array of enums or `const object`s into a readable string version.
|
* 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 obj - The {@linkcode NormalEnum} to source reverse mappings from
|
||||||
* @param enums - An array of {@linkcode obj}'s values
|
* @param enums - An array of {@linkcode obj}'s values
|
||||||
|
* @param transformValues - An optional function used to transform `obj`'s values into strings.
|
||||||
* @returns The stringified representation of `enums`.
|
* @returns The stringified representation of `enums`.
|
||||||
* @example
|
* @example
|
||||||
* ```ts
|
* ```ts
|
||||||
@ -86,12 +87,16 @@ export function getEnumStr<E extends EnumOrObject>(
|
|||||||
* console.log(stringifyEnumArray(fakeEnum, [fakeEnum.ONE, fakeEnum.TWO, fakeEnum.THREE])); // Output: "[ONE, TWO, THREE] (=[1, 2, 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 {
|
export function stringifyEnumArray<E extends EnumOrObject>(
|
||||||
|
obj: E,
|
||||||
|
enums: ObjectValues<E>[],
|
||||||
|
transformValues?: (val: (typeof enums)[number]) => string,
|
||||||
|
): string {
|
||||||
if (obj.length === 0) {
|
if (obj.length === 0) {
|
||||||
return "[]";
|
return "[]";
|
||||||
}
|
}
|
||||||
|
|
||||||
const vals = enums.slice();
|
const vals = transformValues ? enums.map(transformValues) : enums;
|
||||||
/** An array of string names */
|
/** An array of string names */
|
||||||
let names: string[];
|
let names: string[];
|
||||||
|
|
||||||
|
65
test/test-utils/utils/reward-test-utils.ts
Normal file
65
test/test-utils/utils/reward-test-utils.ts
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import type { HeldItemId } from "#enums/held-item-id";
|
||||||
|
import type { RewardId } from "#enums/reward-id";
|
||||||
|
import type { TrainerItemId } from "#enums/trainer-item-id";
|
||||||
|
import { allRewards, type allRewardsType } from "#items/all-rewards";
|
||||||
|
import { HeldItemReward, type Reward, RewardGenerator, TrainerItemReward } from "#items/reward";
|
||||||
|
import { isHeldItemId, isTrainerItemId } from "#items/reward-utils";
|
||||||
|
import type { RewardPoolId, RewardSpecs } from "#types/rewards";
|
||||||
|
|
||||||
|
// Type used to convert allRewards into a type
|
||||||
|
type allRewardsRewardType = {
|
||||||
|
[k in keyof allRewardsType]: allRewardsType[k] extends RewardGenerator
|
||||||
|
? ReturnType<allRewardsType[k]["generateReward"]>
|
||||||
|
: allRewardsType[k];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dynamically generate a {@linkcode Reward} from a given RewardSpecs.
|
||||||
|
* @param specs - The {@linkcode RewardSpecs} used to generate the reward
|
||||||
|
* @returns The generated {@linkcode Reward}, or `null` if no reward could be generated
|
||||||
|
* @todo Remove `null` from signature eventually
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* const reward = generateRewardForTest({id: RewardId.BERRY, args: BerryType.SITRUS});
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function generateRewardForTest<T extends RewardId>(specs: RewardSpecs<T>): allRewardsRewardType[T] | null;
|
||||||
|
/**
|
||||||
|
* Dynamically generate a {@linkcode Reward} from a given HeldItemId.
|
||||||
|
* @param id - The {@linkcode HeldItemId | ID} of the Held item to generate
|
||||||
|
* @returns The generated {@linkcode HeldItemReward}, or `null` if no reward could be generated
|
||||||
|
* @todo Remove `null` from signature eventually
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* const reward = generateRewardForTest(HeldItemId.REVIVER_SEED);
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function generateRewardForTest<T extends HeldItemId>(id: RewardSpecs<T>): HeldItemReward | null;
|
||||||
|
/**
|
||||||
|
* Dynamically generate a {@linkcode Reward} from a given TrainerItemId.
|
||||||
|
* @param id - The {@linkcode TrainerItemId | ID} of the Trainer item to generate
|
||||||
|
* @returns The generated {@linkcode TrainerItemReward}, or `null` if no reward could be generated
|
||||||
|
* @todo Remove `null` from signature eventually
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* const reward = generateRewardForTest(TrainerItemId.HEALING_CHARM);
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function generateRewardForTest<T extends TrainerItemId>(specs: RewardSpecs<T>): TrainerItemReward | null;
|
||||||
|
export function generateRewardForTest(specs: RewardSpecs): Reward | null {
|
||||||
|
// Destructure specs into individual parameters
|
||||||
|
const pregenArgs = typeof specs === "object" ? specs.args : undefined;
|
||||||
|
const id: RewardPoolId = typeof specs === "object" ? specs.id : specs;
|
||||||
|
|
||||||
|
if (isHeldItemId(id)) {
|
||||||
|
return new HeldItemReward(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isTrainerItemId(id)) {
|
||||||
|
return new TrainerItemReward(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
const rewardFunc = allRewards[id];
|
||||||
|
// @ts-expect-error - We enforce call safety using overloads
|
||||||
|
return rewardFunc instanceof RewardGenerator ? rewardFunc.generateReward(pregenArgs) : rewardFunc;
|
||||||
|
}
|
@ -54,6 +54,7 @@
|
|||||||
"#utils/*": ["./utils/*.ts"],
|
"#utils/*": ["./utils/*.ts"],
|
||||||
"#data/*": ["./data/pokemon-forms/*.ts", "./data/pokemon/*.ts", "./data/*.ts"],
|
"#data/*": ["./data/pokemon-forms/*.ts", "./data/pokemon/*.ts", "./data/*.ts"],
|
||||||
"#test/*": ["../test/*.ts"],
|
"#test/*": ["../test/*.ts"],
|
||||||
|
"#test/test-utils/*": ["../test/test-utils/utils/*.ts", "../test/test-utils/*.ts"],
|
||||||
"#app/*": ["*.ts"]
|
"#app/*": ["*.ts"]
|
||||||
},
|
},
|
||||||
"outDir": "./build",
|
"outDir": "./build",
|
||||||
|
Loading…
Reference in New Issue
Block a user