Skip to content

Commit d6db064

Browse files
committed
feat: add enums.find method
1 parent 85957a8 commit d6db064

File tree

11 files changed

+160
-13
lines changed

11 files changed

+160
-13
lines changed

src/enum-collection.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,10 @@ export class EnumCollectionClass<
151151
return this.items.has(keyOrValue);
152152
}
153153

154+
find(...rest: Parameters<EnumItemsArray<T, K, V>['find']>): EnumItemClass<T[K], K, V> | undefined {
155+
return this.items.find(...rest) as EnumItemClass<T[K], K, V> | undefined;
156+
}
157+
154158
toList(): ListItem<V, 'value', 'label'>[];
155159
toList<
156160
FOV extends string | ((item: EnumItemClass<T[K], K, V>) => string),

src/enum-item.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export class EnumItemClass<
4444
* - **EN:** A boolean value indicates that this is an enum item instance.
4545
* - **CN:** 布尔值,表示这是一个枚举项实例
4646
*/
47-
readonly [IS_ENUM_ITEM] = true;
47+
private readonly [IS_ENUM_ITEM] = true;
4848
/**
4949
* Auto convert to a correct primitive type. This method is called when the object is used in a
5050
* context that requires a primitive value.
@@ -55,7 +55,8 @@ export class EnumItemClass<
5555
*
5656
* @returns V | string
5757
*/
58-
[Symbol.toPrimitive](this: EnumItemClass<T, K, V>, hint: 'number' | 'string' | 'default'): V | string {
58+
// @ts-expect-error: because don't want show `Symbol` in vscode's intellisense, it should work in background
59+
private [Symbol.toPrimitive](this: EnumItemClass<T, K, V>, hint: 'number' | 'string' | 'default'): V | string {
5960
if (hint === 'number') {
6061
// for cases like Number(value) or +value
6162
return this.valueOf();

src/enum-items.ts

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ import type {
55
EnumKey,
66
EnumValue,
77
FindEnumKeyByValue,
8+
FindKeyByMeta,
89
FindLabelByValue,
10+
FindValueByKey,
11+
FindValueByMeta,
912
ListItem,
1013
MenuItemOption,
1114
PrimitiveOf,
@@ -26,7 +29,7 @@ import { IS_ENUM_ITEMS } from './utils';
2629
* @implements {IEnumValues<T, K, V>}
2730
*/
2831
export class EnumItemsArray<
29-
T extends EnumInit<K, V>,
32+
const T extends EnumInit<K, V>,
3033
K extends EnumKey<T> = EnumKey<T>,
3134
V extends EnumValue = ValueTypeFromSingleInit<T[K], K>,
3235
>
@@ -127,6 +130,32 @@ export class EnumItemsArray<
127130
return this.some((i) => i.value === keyOrValue || i.key === keyOrValue);
128131
}
129132

133+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
134+
find: IEnumItems<T, K, V>['find'] = (fieldOrPredicate: any, valueOrThis?: EnumValue | any): any => {
135+
if (typeof fieldOrPredicate === 'function') {
136+
const predicate = fieldOrPredicate;
137+
const thisArg = valueOrThis;
138+
// If the first argument is a function, use it as a predicate
139+
return Array.prototype.find.call(this, predicate, thisArg);
140+
} else {
141+
// Otherwise, treat it as a field name and value
142+
const field: string = fieldOrPredicate;
143+
const value = valueOrThis;
144+
return Array.prototype.find.call(this, (item) => {
145+
if (field === 'key' || field === 'value') {
146+
return item[field] === value;
147+
} else if (field === 'label') {
148+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
149+
return (item.raw as any)?.label === value || item.label === value;
150+
} else {
151+
// For other fields, use the raw object to find
152+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
153+
return (item.raw as any)?.[field] === value;
154+
}
155+
});
156+
}
157+
};
158+
130159
toList(): ListItem<V, 'value', 'label'>[];
131160
toList<
132161
FOV extends string | ((item: EnumItemClass<T[K], K, V>) => string),
@@ -341,6 +370,48 @@ export interface IEnumItems<
341370
*/
342371
has(keyOrValue?: string | V): boolean;
343372

373+
/**
374+
* **EN:** Find an enumeration item by key or value, or by custom meta fields
375+
*
376+
* **CN:** 通过key或value查找枚举项,或通过自定义元字段查找
377+
*
378+
* @param field The field to search by | 要查找的字段
379+
* @param value The value to search | 要查找的值
380+
*
381+
* @returns The found enumeration item or undefined if not found | 找到的枚举项,如果未找到则返回 undefined
382+
*/
383+
find<FK extends 'key' | 'value' | 'label' | Exclude<keyof T[keyof T], 'key' | 'value' | 'label'>, FV>(
384+
field: FK,
385+
value: FV
386+
): FK extends 'key'
387+
? FV extends K
388+
? // @ts-expect-error: because the type infer is not clever enough, FV here should be one of K
389+
EnumItemClass<T[FV], FV, FindValueByKey<T, FV>>
390+
: EnumItemClass<T[K], K, V> | undefined
391+
: FK extends 'value'
392+
? FV extends V
393+
? // @ts-expect-error: because the type infer is not clever enough, FV here should be one of V
394+
EnumItemClass<T[FindEnumKeyByValue<T, FV>], FindEnumKeyByValue<T, FV>, FV>
395+
: EnumItemClass<T[K], K, V> | undefined
396+
: FK extends 'label'
397+
? EnumItemClass<T[K], K, V> | undefined
398+
: // @ts-expect-error: because the type infer is not clever enough, FK here should be one of keyof Raw
399+
FV extends T[keyof T][FK]
400+
? // @ts-expect-error: because the type infer is not clever enough, FV here should be one of T[keyof T][FK]
401+
EnumItemClass<T[FindKeyByMeta<T, FK, FV>], FindKeyByMeta<T, FK, FV>, FindValueByMeta<T, FK, FV>>
402+
: EnumItemClass<T[K], K, V> | undefined;
403+
// Copy the Array.find type definition and extend another override
404+
find<S extends EnumItemClass<T[K], K, V>>(
405+
predicate: (value: EnumItemClass<T[K], K, V>, index: number, obj: EnumItemClass<T[K], K, V>[]) => value is S,
406+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
407+
thisArg?: any
408+
): S | undefined;
409+
find(
410+
predicate: (value: EnumItemClass<T[K], K, V>, index: number, obj: EnumItemClass<T[K], K, V>[]) => unknown,
411+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
412+
thisArg?: any
413+
): EnumItemClass<T[K], K, V> | undefined;
414+
344415
/**
345416
* - **EN:** Generate an object array containing all enumeration items
346417
* - **CN:** 生成一个对象数组,包含所有的枚举项

src/enum.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,8 @@ export type IEnum<
201201
* - **EN:** A boolean value indicates that this is an Enum.
202202
* - **CN:** 布尔值,表示这是一个枚举类
203203
*/
204-
[IS_ENUM]: true;
204+
// this flag exists but is removed from interface, as it's replaced with isEnum method
205+
// [IS_ENUM]: true;
205206
} & {
206207
// Add enum item values, just like native enums
207208
[key in K]: ValueTypeFromSingleInit<T[key], key, T[K] extends number | undefined ? number : key>;

src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ export type {
88
MenuItemOption,
99
ColumnFilterItem,
1010
FindEnumKeyByValue,
11+
FindValueByKey,
12+
FindLabelByValue,
1113
ArrayToMap,
1214
} from './types';
1315
export type { LocalizeInterface } from './localize-interface';

src/types.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,35 @@ export type FindLabelByValue<T, V extends EnumValue, RAW = T[FindEnumKeyByValue<
174174
? string
175175
: FindEnumKeyByValue<T, V>;
176176

177+
/**
178+
* - **EN:** Find the value of the enumeration item by key
179+
* - **CN:** 通过key查找枚举项的值
180+
*
181+
* @template T Enum collection initialization data type | 枚举集合初始化数据的类型
182+
* @template K Enum key type | 枚举key的类型
183+
*/
184+
export type FindValueByKey<T, K extends EnumKey<T>> = T[K] extends EnumValue
185+
? T[K]
186+
: T[K] extends StandardEnumItemInit<infer V>
187+
? V
188+
: T[K] extends ValueOnlyEnumItemInit<infer V>
189+
? V
190+
: T[K] extends LabelOnlyEnumItemInit
191+
? K
192+
: T[K] extends CompactEnumItemInit
193+
? K
194+
: T[K] extends undefined
195+
? K
196+
: never;
197+
198+
export type FindKeyByMeta<T, MK extends keyof T[keyof T], MV> = {
199+
[K in keyof T]: T[K] extends Record<MK, MV> ? K : never;
200+
}[keyof T];
201+
202+
export type FindValueByMeta<T, MK extends keyof T[keyof T], MV> = {
203+
[K in keyof T]: T[K] extends Record<MK, MV> ? T[K][MK] : never;
204+
}[keyof T];
205+
177206
export type PrimitiveOf<T> = T extends string
178207
? string
179208
: T extends number

test/engines/playwright.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ export class PlaywrightEngine extends TestEngineBase {
9090
const globalClosure = { Enum, EnumExtensionClass, localizer, ...utils };
9191
const initialState = parse(resultStr, {
9292
closure: globalClosure,
93-
// debug: true,
93+
debug: true,
9494
prettyPrint: false,
9595
});
9696
// Restore the lang to the Enum.localize

test/test-suites/enum-collection.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@ const testEnumCollection = (engine: TestEngineBase) => {
6363
engine.expect(week.label(1)).toBe(week.items.label(1));
6464
engine.expect(week.key(1)).toBe(week.items.key(1));
6565
engine.expect(week.has(1)).toBe(week.items.has(1));
66+
engine.expect(week.find('key', 'Monday')).toBe(week.items.find('key', 'Monday'));
67+
engine.expect(week.find('value', 1)).toBe(week.items.find('value', 1));
68+
engine.expect(week.find('label', 'weekday.monday')).toBe(week.items.find('label', 'weekday.monday'));
69+
engine.expect(week.find('status', 'success')).toBe(week.items.find('status', 'success'));
6670
engine.expect(week.raw()).toBe(week.items.raw());
6771
engine.expect(week.toList()).toEqual(week.items.toList());
6872
engine.expect(week.toMenu()).toEqual(week.items.toMenu());
@@ -166,7 +170,7 @@ const testEnumCollection = (engine: TestEngineBase) => {
166170
({ week, IS_ENUM }) => {
167171
// @ts-expect-error: because IS_ENUM is hidden by the interface, but it actually exists
168172
engine.expect(week[IS_ENUM]).toBe(true);
169-
engine.expect(week[IS_ENUM_IN_NODE]).toBe(true);
173+
engine.expect(IS_ENUM_IN_NODE in week && week[IS_ENUM_IN_NODE]).toBe(true);
170174
// @ts-expect-error: because IS_ENUM and Symbol.for('[IsEnum]') are equal
171175
engine.expect(week[Symbol.for('[IsEnum]')]).toBe(true);
172176
}

test/test-suites/enum-items.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,38 @@ export function addEnumValuesTestSuite(engine: TestEngineBase) {
5454
}
5555
);
5656

57+
engine.test(
58+
'enums.find should be able to find enum item by key, value or custom meta fields',
59+
({ EnumPlus: { Enum }, WeekConfig: { StandardWeekConfig, WeekCompactConfig } }) => {
60+
const weekEnum = Enum(StandardWeekConfig);
61+
const compactWeekEnum = Enum(WeekCompactConfig);
62+
return { weekEnum, compactWeekEnum };
63+
},
64+
({ weekEnum, compactWeekEnum }) => {
65+
engine.expect(weekEnum.find('key', 'Monday')).toBe(weekEnum.items[1]);
66+
engine.expect(weekEnum.find('key', 'Saturday')).toBe(weekEnum.items[6]);
67+
engine.expect(weekEnum.find('key', 'Invalid Key')).toBe(undefined);
68+
engine.expect(weekEnum.find('value', 1)).toBe(weekEnum.items[1]);
69+
engine.expect(weekEnum.find('value', 6)).toBe(weekEnum.items[6]);
70+
engine.expect(weekEnum.find('value', 99)).toBe(undefined);
71+
engine.expect(weekEnum.find('label', 'weekday.monday')).toBe(weekEnum.items[1]);
72+
engine.expect(weekEnum.find('label', 'weekday.saturday')).toBe(weekEnum.items[6]);
73+
engine.expect(weekEnum.find('label', 'Invalid Label')).toBe(undefined);
74+
engine.expect(compactWeekEnum.find('key', 'Monday')).toBe(compactWeekEnum.items[1]);
75+
engine.expect(compactWeekEnum.find('key', 'Invalid Key')).toBe(undefined);
76+
engine.expect(compactWeekEnum.find('value', 1)).toBe(undefined);
77+
engine.expect(compactWeekEnum.find('value', 99)).toBe(undefined);
78+
engine.expect(compactWeekEnum.find('label', 'weekday.monday')).toBe(undefined);
79+
80+
// Custom meta field
81+
engine.expect(weekEnum.find('status', 'error')).toEqual(weekEnum.items[0]);
82+
engine.expect(weekEnum.find('status', 'warning')).toEqual(weekEnum.items[1]);
83+
engine.expect(weekEnum.find('status', 'success')).toEqual(weekEnum.items[3]);
84+
engine.expect(weekEnum.find('status', 'invalid')).toEqual(undefined);
85+
engine.expect(compactWeekEnum.find('status' as 'key', 'success')).toEqual(undefined);
86+
}
87+
);
88+
5789
engine.test(
5890
'enums.toList should generate an object array',
5991
({ EnumPlus: { Enum }, WeekConfig: { StandardWeekConfig, locales } }) => {

0 commit comments

Comments
 (0)