mirror of
https://github.com/Kir-Antipov/mc-publish.git
synced 2024-11-29 03:40:56 -05:00
Implemented Object
-like Enum
interface
This commit is contained in:
parent
5fe6c0ba36
commit
64f717d127
2 changed files with 273 additions and 0 deletions
154
src/utils/enum/enum.ts
Normal file
154
src/utils/enum/enum.ts
Normal file
|
@ -0,0 +1,154 @@
|
||||||
|
import { IGNORE_CASE_AND_NON_WORD_CHARACTERS_EQUALITY_COMPARER, IGNORE_CASE_EQUALITY_COMPARER, IGNORE_NON_WORD_CHARACTERS_EQUALITY_COMPARER, ORDINAL_EQUALITY_COMPARER } from "@/utils/comparison";
|
||||||
|
import { EnumDescriptor, getEnumDescriptorByUnderlyingType } from "./descriptors";
|
||||||
|
import { ConstructedEnum, DynamicEnum, DynamicEnumOptions } from "./dynamic-enum";
|
||||||
|
import { enumKeys } from "./enum-key";
|
||||||
|
import { enumValues } from "./enum-value";
|
||||||
|
import { enumEntries } from "./enum-entry";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The options to use when creating the new enum.
|
||||||
|
*/
|
||||||
|
export interface EnumOptions extends DynamicEnumOptions {
|
||||||
|
/**
|
||||||
|
* Indicates whether to ignore the case when comparing enum keys.
|
||||||
|
*/
|
||||||
|
ignoreCase?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates whether to ignore non-word characters when comparing enum keys.
|
||||||
|
*/
|
||||||
|
ignoreNonWordCharacters?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether the given `value` contains the specified `flag`.
|
||||||
|
*
|
||||||
|
* @template T - Type of the enum.
|
||||||
|
*
|
||||||
|
* @param value - The value to check for the presence of the flag.
|
||||||
|
* @param flag - The flag to check for.
|
||||||
|
*
|
||||||
|
* @returns `true` if the value has the flag; otherwise, `false`.
|
||||||
|
*/
|
||||||
|
export function hasFlag<T>(value: T, flag: T): boolean {
|
||||||
|
const descriptor = getEnumDescriptorByUnderlyingType(typeof flag) as EnumDescriptor<T>;
|
||||||
|
return !!descriptor?.hasFlag(value, flag);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new enum object from the specified `enumFactory` with the specified `options`.
|
||||||
|
*
|
||||||
|
* @template T - Type of the enum.
|
||||||
|
*
|
||||||
|
* @param enumFactory - The enum factory to use for the new enum.
|
||||||
|
* @param options - The options to use when creating the new enum.
|
||||||
|
*
|
||||||
|
* @returns The constructed enum object.
|
||||||
|
*/
|
||||||
|
export function createEnum<T>(enumFactory: () => T, options?: EnumOptions): ConstructedEnum<T>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new enum object from the specified `enumFactory` with the specified `options`.
|
||||||
|
*
|
||||||
|
* @template T - Type of the enum.
|
||||||
|
*
|
||||||
|
* @param enumFactory - The enum factory to use for the new enum.
|
||||||
|
* @param options - The options to use when creating the new enum.
|
||||||
|
*
|
||||||
|
* @returns The constructed enum object.
|
||||||
|
*/
|
||||||
|
export function createEnum<T, U>(enumFactory: () => T, options: EnumOptions, methods: U): ConstructedEnum<T> & Readonly<U>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new enum object from the specified underlying enum with the specified `options`.
|
||||||
|
*
|
||||||
|
* @template T - Type of the enum.
|
||||||
|
*
|
||||||
|
* @param underlyingEnum - The underlying enum to use for the new enum.
|
||||||
|
* @param options - The options to use when creating the new enum.
|
||||||
|
*
|
||||||
|
* @returns The constructed enum object.
|
||||||
|
*/
|
||||||
|
export function createEnum<T>(underlyingEnum: T, options?: EnumOptions): ConstructedEnum<T>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new enum object from the specified underlying enum with the specified `options`.
|
||||||
|
*
|
||||||
|
* @template T - Type of the enum.
|
||||||
|
*
|
||||||
|
* @param underlyingEnum - The underlying enum to use for the new enum.
|
||||||
|
* @param options - The options to use when creating the new enum.
|
||||||
|
*
|
||||||
|
* @returns The constructed enum object.
|
||||||
|
*/
|
||||||
|
export function createEnum<T, U>(underlyingEnum: T, options: EnumOptions, methods: U): ConstructedEnum<T> & Readonly<U>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new enum object from the specified `enumFactory` or `underlyingEnum` with the specified `options`.
|
||||||
|
*
|
||||||
|
* @template T - Type of the enum.
|
||||||
|
*
|
||||||
|
* @param e - The enum factory or underlying enum to use for the new enum.
|
||||||
|
* @param options - The options to use when creating the new enum.
|
||||||
|
*
|
||||||
|
* @returns The constructed enum object.
|
||||||
|
*/
|
||||||
|
export function createEnum<T>(e: T | (() => T), options?: EnumOptions, methods?: unknown): ConstructedEnum<T> {
|
||||||
|
const underlyingEnum = typeof e === "function" ? (e as () => T)() : e;
|
||||||
|
const dynamicEnumOptions = toDynamicEnumOptions(options);
|
||||||
|
|
||||||
|
const dynamicEnum = DynamicEnum.create(underlyingEnum, dynamicEnumOptions);
|
||||||
|
if (methods) {
|
||||||
|
Object.assign(dynamicEnum, methods);
|
||||||
|
}
|
||||||
|
|
||||||
|
return dynamicEnum;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts specified `options` into an instance acceptable by the {@link DynamicEnum}'s constructor.
|
||||||
|
*
|
||||||
|
* @param options - The options to be converted.
|
||||||
|
*
|
||||||
|
* @returns The options acceptable by the {@link DynamicEnum}'s constructor.
|
||||||
|
*/
|
||||||
|
function toDynamicEnumOptions(options?: EnumOptions): DynamicEnumOptions {
|
||||||
|
if (!options || (options as DynamicEnumOptions).comparer) {
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
const o = options as EnumOptions;
|
||||||
|
const comparer = o.ignoreCase ? o.ignoreNonWordCharacters
|
||||||
|
? IGNORE_CASE_AND_NON_WORD_CHARACTERS_EQUALITY_COMPARER
|
||||||
|
: IGNORE_CASE_EQUALITY_COMPARER
|
||||||
|
: o.ignoreNonWordCharacters
|
||||||
|
? IGNORE_NON_WORD_CHARACTERS_EQUALITY_COMPARER
|
||||||
|
: ORDINAL_EQUALITY_COMPARER;
|
||||||
|
|
||||||
|
return { ...o, comparer };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An object that emulates the `Object` API for `Enum` objects.
|
||||||
|
*/
|
||||||
|
export const Enum = {
|
||||||
|
hasFlag,
|
||||||
|
create: createEnum,
|
||||||
|
keys: enumKeys,
|
||||||
|
values: enumValues,
|
||||||
|
entries: enumEntries,
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A type that extracts definition of the original enum
|
||||||
|
* from the dynamically created one.
|
||||||
|
*
|
||||||
|
* @template T - Type of the dynamically created enum.
|
||||||
|
*/
|
||||||
|
export type Enum<T> = {
|
||||||
|
[K in keyof T]: K extends "size" | "underlyingType" | typeof Symbol.toStringTag
|
||||||
|
? never
|
||||||
|
: T[K] extends (...args: unknown[]) => unknown
|
||||||
|
? never
|
||||||
|
: T[K];
|
||||||
|
}[keyof T];
|
119
tests/unit/utils/enum/enum.spec.ts
Normal file
119
tests/unit/utils/enum/enum.spec.ts
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
import { enumEntries } from "@/utils/enum/enum-entry";
|
||||||
|
import { enumKeys } from "@/utils/enum/enum-key";
|
||||||
|
import { enumValues } from "@/utils/enum/enum-value";
|
||||||
|
import { DynamicEnum } from "@/utils/enum/dynamic-enum";
|
||||||
|
import { hasFlag, Enum, createEnum } from "@/utils/enum/enum";
|
||||||
|
|
||||||
|
describe("hasFlag", () => {
|
||||||
|
test("returns true if a flag is set", () => {
|
||||||
|
expect(hasFlag(3, 2)).toBe(true);
|
||||||
|
expect(hasFlag(3n, 2n)).toBe(true);
|
||||||
|
expect(hasFlag(true, true)).toBe(true);
|
||||||
|
expect(hasFlag("value1, value2", "value2")).toBe(true);
|
||||||
|
expect(hasFlag("value1 | value2", "value2")).toBe(true);
|
||||||
|
expect(hasFlag("value1|value2", "value2")).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("returns false if a flag is not set", () => {
|
||||||
|
expect(hasFlag(3, 4)).toBe(false);
|
||||||
|
expect(hasFlag(3n, 4n)).toBe(false);
|
||||||
|
expect(hasFlag(false, true)).toBe(false);
|
||||||
|
expect(hasFlag("value1, value2", "value3")).toBe(false);
|
||||||
|
expect(hasFlag("value1 | value2", "value3")).toBe(false);
|
||||||
|
expect(hasFlag("value1|value2", "value3")).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("returns false if a value cannot contain flags", () => {
|
||||||
|
expect(hasFlag({}, {})).toBe(false);
|
||||||
|
expect(hasFlag([], [])).toBe(false);
|
||||||
|
expect(hasFlag(Symbol("a"), Symbol("b"))).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("createEnum", () => {
|
||||||
|
test("creates an enum when a plain object is given", () => {
|
||||||
|
const e = createEnum({ A: "A", B: "B" });
|
||||||
|
|
||||||
|
expect(e).toBeInstanceOf(DynamicEnum);
|
||||||
|
expect(e.A).toBe("A");
|
||||||
|
expect(e.B).toBe("B");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("creates an enum when a plain enum is given", () => {
|
||||||
|
enum TestEnum {
|
||||||
|
A = 1,
|
||||||
|
B = 2,
|
||||||
|
}
|
||||||
|
const e = createEnum(TestEnum);
|
||||||
|
|
||||||
|
expect(e).toBeInstanceOf(DynamicEnum);
|
||||||
|
expect(e.A).toBe(1);
|
||||||
|
expect(e.B).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("creates an enum when a function returning an object is given", () => {
|
||||||
|
const e = createEnum(() => ({ A: "A", B: "B" }));
|
||||||
|
|
||||||
|
expect(e).toBeInstanceOf(DynamicEnum);
|
||||||
|
expect(e.A).toBe("A");
|
||||||
|
expect(e.B).toBe("B");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("creates an enum with custom methods", () => {
|
||||||
|
const e = createEnum({ A: "A", B: "B" }, {}, { customMethod: () => "custom" });
|
||||||
|
|
||||||
|
expect(e.customMethod()).toBe("custom");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("creates an enum with 'ignoreCase' option", () => {
|
||||||
|
const e = createEnum({ A: "A", B: "B", Foo: "Foo" }, { ignoreCase: true });
|
||||||
|
|
||||||
|
expect(e.get("A")).toBe("A");
|
||||||
|
expect(e.get("a")).toBe("A");
|
||||||
|
expect(e.get("B")).toBe("B");
|
||||||
|
expect(e.get("b")).toBe("B");
|
||||||
|
expect(e.get("Foo")).toBe("Foo");
|
||||||
|
expect(e.get("foo")).toBe("Foo");
|
||||||
|
expect(e.get("FOO")).toBe("Foo");
|
||||||
|
expect(e.get("FoO")).toBe("Foo");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("creates an enum with 'ignoreNonWordCharacters' option", () => {
|
||||||
|
const e = createEnum({ "a-b": "a-b", "C_D": "C_D" }, { ignoreNonWordCharacters: true });
|
||||||
|
|
||||||
|
expect(e.get("ab")).toBe("a-b");
|
||||||
|
expect(e.get("CD")).toBe("C_D");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Enum", () => {
|
||||||
|
describe("hasFlag", () => {
|
||||||
|
test("redirects the call to 'hasFlag'", () => {
|
||||||
|
expect(Enum.hasFlag).toBe(hasFlag);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("create", () => {
|
||||||
|
test("redirects the call to 'createEnum'", () => {
|
||||||
|
expect(Enum.create).toBe(createEnum);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("keys", () => {
|
||||||
|
test("redirects the call to 'enumKeys'", () => {
|
||||||
|
expect(Enum.keys).toBe(enumKeys);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("values", () => {
|
||||||
|
test("redirects the call to 'enumValues'", () => {
|
||||||
|
expect(Enum.values).toBe(enumValues);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("entries", () => {
|
||||||
|
test("redirects the call to 'enumEntries'", () => {
|
||||||
|
expect(Enum.entries).toBe(enumEntries);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in a new issue