From 489295f2c0bb0b51afa8548fe8bad0e5979a5c14 Mon Sep 17 00:00:00 2001 From: Bertie690 Date: Mon, 23 Jun 2025 22:50:21 -0400 Subject: [PATCH] Fiexd up tests --- src/@types/type-helpers.ts | 19 +++++++++++++++---- src/utils/enums.ts | 22 ++++++++++++---------- test/types/enum-types.test-d.ts | 12 +++++++++--- 3 files changed, 36 insertions(+), 17 deletions(-) diff --git a/src/@types/type-helpers.ts b/src/@types/type-helpers.ts index 2d00b1faf4a..375d8f64de3 100644 --- a/src/@types/type-helpers.ts +++ b/src/@types/type-helpers.ts @@ -2,8 +2,10 @@ * A collection of custom utility types that aid in type checking and ensuring strict type conformity */ -// biome-ignore lint/correctness/noUnusedImports: Used in a tsdoc comment +// biome-ignore-start lint/correctness/noUnusedImports: Used in a tsdoc comment +import type { EnumValues } from "#app/@types/enum-types"; import type { AbAttr } from "./ability-types"; +// biome-ignore-end lint/correctness/noUnusedImports: Used in a tsdoc comment /** * Exactly matches the type of the argument, preventing adding additional properties. @@ -11,7 +13,7 @@ import type { AbAttr } from "./ability-types"; * ⚠️ Should never be used with `extends`, as this will nullify the exactness of the type. * * As an example, used to ensure that the parameters of {@linkcode AbAttr.canApply} and {@linkcode AbAttr.getTriggerMessage} are compatible with - * the type of the apply method + * the type of its {@linkcode AbAttr.apply | apply} method. * * @typeParam T - The type to match exactly */ @@ -26,9 +28,18 @@ export type Exact = { export type Closed = X; /** - * Remove `readonly` from all properties of the provided type - * @typeParam T - The type to make mutable + * Remove `readonly` from all properties of the provided type. + * @typeParam T - The type to make mutable. */ export type Mutable = { -readonly [P in keyof T]: T[P]; }; + +/** + * Type helper to obtain the keys associated with a given value inside a `const object`. + * @typeParam O - The type of the object + * @typeParam V - The type of one of O's values + */ +export type InferKeys, V extends EnumValues> = { + [K in keyof O]: O[K] extends V ? K : never; +}[keyof O]; diff --git a/src/utils/enums.ts b/src/utils/enums.ts index 7193d014ccc..c13b5dc780e 100644 --- a/src/utils/enums.ts +++ b/src/utils/enums.ts @@ -1,9 +1,10 @@ import type { EnumOrObject, EnumValues, TSNumericEnum, NormalEnum } from "#app/@types/enum-types"; +import type { InferKeys } from "#app/@types/type-helpers"; /** * Return the string keys of an Enum object, excluding reverse-mapped numbers. - * @param enumType - The numeric enum to retrieve keys for. - * @returns An ordered array of all of `enumType`'s string keys. + * @param enumType - The numeric enum to retrieve keys for + * @returns An ordered array of all of `enumType`'s string keys * @example * enum fruit { * apple = 1, @@ -23,8 +24,8 @@ export function getEnumKeys(enumType: TSNumericEnum): /** * Return the numeric values of a numeric Enum object, excluding reverse-mapped strings. - * @param enumType - The enum object to retrieve keys for. - * @returns An ordered array of all of `enumType`'s number values. + * @param enumType - The enum object to retrieve keys for + * @returns An ordered array of all of `enumType`'s number values * @example * enum fruit { * apple = 1, @@ -46,9 +47,9 @@ export function getEnumValues(enumType: TSNumericEnum /** * Return the name of the key that matches the given Enum value. * Can be used to emulate Typescript reverse mapping for `const object`s or string enums. - * @param object - The {@linkcode NormalEnum} to check. - * @param val - The value to get the key of. - * @returns The name of the key with the specified value. + * @param object - The {@linkcode NormalEnum} to check + * @param val - The value to get the key of + * @returns The name of the key with the specified value * @example * const thing = { * one: 1, @@ -57,15 +58,16 @@ export function getEnumValues(enumType: TSNumericEnum * console.log(enumValueToKey(thing, 2)); // output: "two" * @throws Error if an invalid enum value is passed to the function * @remarks - * If multiple keys map to the same value, the first one (in insertion order) will be retrieved. + * If multiple keys map to the same value, the first one (in insertion order) will be retrieved, + * but the return type will be the union of ALL their corresponding keys. */ export function enumValueToKey>( object: NormalEnum, val: V, -): keyof T { +): InferKeys { for (const [key, value] of Object.entries(object)) { if (val === value) { - return key; + return key as InferKeys; } } throw new Error(`Invalid value passed to \`enumValueToKey\`! Value: ${val}`); diff --git a/test/types/enum-types.test-d.ts b/test/types/enum-types.test-d.ts index eb1621596bf..bdc7146d41a 100644 --- a/test/types/enum-types.test-d.ts +++ b/test/types/enum-types.test-d.ts @@ -1,7 +1,7 @@ import type { EnumOrObject, EnumValues, TSNumericEnum, NormalEnum } from "#app/@types/enum-types"; import type { getEnumKeys, getEnumValues } from "#app/utils/enums"; -import { enumValueToKey } from "#app/utils/enums"; +import type { enumValueToKey } from "#app/utils/enums"; import { expectTypeOf, describe, it } from "vitest"; @@ -94,8 +94,14 @@ describe("Enum Functions", () => { describe("enumValueToKey", () => { it("should retrieve values for a given key", () => { - // @ts-expect-error oopsie - expectTypeOf(enumValueToKey(testEnumString, testEnumString.testS1)).toEqualTypeOf<"testS1">(); + expectTypeOf< + typeof enumValueToKey + >().returns.toEqualTypeOf<"testS1">(); + expectTypeOf>().returns.toEqualTypeOf< + "testS1" | "testS2" + >(); + expectTypeOf>().returns.toEqualTypeOf<"testON1">(); + expectTypeOf>().returns.toEqualTypeOf<"testON1" | "testON2">(); }); }); });