From 41d4de590e6c2a428aa58dbaa459db7ac9710bec Mon Sep 17 00:00:00 2001 From: Kir_Antipov Date: Wed, 18 Jan 2023 12:28:01 +0000 Subject: [PATCH] Implemented enum descriptor for string enums --- .../enum/descriptors/string-descriptor.ts | 68 +++++++++++++++++++ .../descriptors/string-descriptor.spec.ts | 47 +++++++++++++ 2 files changed, 115 insertions(+) create mode 100644 src/utils/enum/descriptors/string-descriptor.ts create mode 100644 tests/unit/utils/enum/descriptors/string-descriptor.spec.ts diff --git a/src/utils/enum/descriptors/string-descriptor.ts b/src/utils/enum/descriptors/string-descriptor.ts new file mode 100644 index 0000000..ab27084 --- /dev/null +++ b/src/utils/enum/descriptors/string-descriptor.ts @@ -0,0 +1,68 @@ +import { split } from "@/utils/string-utils"; +import { ENUM_SEPARATORS, DEFAULT_ENUM_SEPARATOR } from "../enum-separators"; +import { EnumDescriptor } from "./enum-descriptor"; + +/** + * This descriptor is used to describe a set of flags stored as a `string` value. + * + * @remarks + * + * It's super inefficient, when it comes to flags, because the whole concept + * of string flags just seems too shady to me to optimize for these scenarios. + * So, string enums are ok, but string enums with flags are not recommended. + */ +export class StringDescriptor implements EnumDescriptor { + /** + * @inheritdoc + */ + get name(): "string" { + return "string"; + } + + /** + * @inheritdoc + */ + get defaultValue(): string { + return ""; + } + + /** + * @inheritdoc + */ + hasFlag(value: string, flag: string): boolean { + if (flag === this.defaultValue || flag === value) { + return true; + } + + if (!value) { + return false; + } + + const flags = split(value, ENUM_SEPARATORS, { trimEntries: true, removeEmptyEntries: true }); + return flags.includes(flag); + } + + /** + * @inheritdoc + */ + addFlag(value: string, flag: string): string { + value = this.removeFlag(value, flag); + return value ? `${value}${DEFAULT_ENUM_SEPARATOR} ${flag}` : value; + } + + /** + * @inheritdoc + */ + removeFlag(value: string, flag: string): string { + if (value === this.defaultValue || flag === this.defaultValue) { + return value; + } + + if (value === flag) { + return this.defaultValue; + } + + const flags = split(value, ENUM_SEPARATORS, { trimEntries: true, removeEmptyEntries: true }); + return flags.filter(x => x !== flag).join(DEFAULT_ENUM_SEPARATOR + " "); + } +} diff --git a/tests/unit/utils/enum/descriptors/string-descriptor.spec.ts b/tests/unit/utils/enum/descriptors/string-descriptor.spec.ts new file mode 100644 index 0000000..12e5cf3 --- /dev/null +++ b/tests/unit/utils/enum/descriptors/string-descriptor.spec.ts @@ -0,0 +1,47 @@ +import { StringDescriptor } from "@/utils/enum/descriptors/string-descriptor"; + +describe("StringDescriptor", () => { + const descriptor = new StringDescriptor(); + + describe("name", () => { + test("returns 'string' as name", () => { + expect(descriptor.name).toBe("string"); + }); + }); + + describe("defaultValue", () => { + test("returns '' as default value", () => { + expect(descriptor.defaultValue).toBe(""); + }); + }); + + describe("hasFlag", () => { + test("returns true if flag is set", () => { + expect(descriptor.hasFlag("value1, value2, value3", "value2")).toBe(true); + }); + + test("returns false if flag is not set", () => { + expect(descriptor.hasFlag("value1, value2, value3", "value4")).toBe(false); + }); + }); + + describe("addFlag", () => { + test("adds flag to value", () => { + expect(descriptor.addFlag("value1, value2", "value3")).toBe("value1, value2, value3"); + }); + + test("does not add flag if it is already set", () => { + expect(descriptor.addFlag("value1, value2, value3", "value3")).toBe("value1, value2, value3"); + }); + }); + + describe("removeFlag", () => { + test("removes flag from value", () => { + expect(descriptor.removeFlag("value1, value2, value3", "value2")).toBe("value1, value3"); + }); + + test("does not remove flag if it does not exist", () => { + expect(descriptor.removeFlag("value1, value2", "value3")).toBe("value1, value2"); + }); + }); +});