diff --git a/README.md b/README.md index cc9e152..515ca3a 100644 --- a/README.md +++ b/README.md @@ -12,14 +12,13 @@ For optimal type safety, use this library in TypeScript's strict mode. - [Match Data](#match-data) - [Generic Enum Types](#generic-enum-types) - [Mutate Enum Variant](#mutate-enum-variant) -- [Enum Classes](#enum-classes) ### Installation Using with Deno is as simple as adding an import to your code: ```ts -import {/* ... */} from "https://deno.land/x/algebraic_enum/src/mod.ts"; +import /* ... */ "https://deno.land/x/algebraic_enum/src/mod.ts"; ``` For Node.js, you can install with npm: @@ -39,17 +38,17 @@ Note that variant names cannot be `_` as it is reserved. ```ts import { Enum } from "https://deno.land/x/algebraic_enum/src/mod.ts"; -const StatusVariants = { - Success: null, - Failure: null, - Pending: null, -}; +class StatusVariants { + Success = null; + Failure = null; + Pending = null; +} -type Status = Enum; +type Status = Enum; -let status: Status = { Success: null }; +const status: Status = { Success: null }; -let invalidStatus: Status = { Success: null, Failure: null }; +const invalidStatus: Status = { Success: null, Failure: null }; // Compilation error, as `Enum` can only contain exactly one variant ``` @@ -68,50 +67,47 @@ support. ```ts // Simplified version of Enum: -type Status = - | { Success: null } - | { Failure: null } - | { Pending: null }; +type Status = { Success: null } | { Failure: null } | { Pending: null }; ``` For easier enum value construction, you can use the `Enum.factory` function: ```ts -type Status = Enum; -const Status = Enum.factory(StatusVariants); +type Status = Enum; +const Status = () => Enum.factory(StatusVariants); -let success = Status.Success(null); -let failure = Status.Failure(null); -let pending = Status.Pending(null); +const success = Status().Success(); +const failure = Status().Failure(); +const pending = Status().Pending(); ``` ### Different Variant Data Types You can attach data of different data types to each variant of an enum by using -the `ofType` helper function. One restriction is that you cannot use `undefined` -or `void` as your variant data type. +the `Variant` function. One restriction is that you cannot use `undefined` or +`void` as your variant data type. ```ts -import { Enum, ofType } from "https://deno.land/x/algebraic_enum/src/mod.ts"; +import { Enum, Variant } from "https://deno.land/x/algebraic_enum/src/mod.ts"; -const StatusVariants = { - Success: ofType(), - Failure: ofType<{ +class StatusVariants { + Success = Variant(); + Failure = Variant<{ code: number; message: string; - }>(), - Pending: null, -}; + }>(); + Pending = null; +} -type Status = Enum; -const Status = Enum.factory(StatusVariants); +type Status = Enum; +const Status = () => Enum.factory(StatusVariants); -let success = Status.Success("Hello World!"); -let failure = Status.Failure({ +const success = Status().Success("Hello World!"); +const failure = Status().Failure({ code: 404, message: "Not Found", }); -let pending = Status.Pending(null); +const pending = Status().Pending(); ``` ### Match Data @@ -120,7 +116,7 @@ You can use `Enum.match` to determine the correct variant and extract data from your enum: ```ts -let message = Enum.match(status, { +const message = Enum.match(status, { Success: (data) => data, Failure: (data) => data.message, Pending: (data) => "Pending...", @@ -131,7 +127,7 @@ Note that matches need to be exhaustive. You need to exhaust every last possibility in order for the code to be valid. The following code won't compile: ```ts -let code = Enum.match(status, { +const code = Enum.match(status, { Failure: (data) => data.code, // Won't compile because of missing variants }); @@ -155,140 +151,44 @@ if (status.Failure !== undefined) { ### Generic Enum Types -It is possible to create generic enum types. Since TypeScript doesn't support -generic objects, you need to create your variants object without generics. -Instead, mark variants with generic types as `unknown`. When constructing the -enum type itself, you can finally override certain variant data types with the -correct generic type. +It is possible to create generic enum types: ```ts -import { - Enum, - memo, - ofType, -} from "https://deno.land/x/algebraic_enum/src/mod.ts"; - -const StatusVariants = { - Success: ofType(), - Failure: ofType<{ +import { Enum, Variant } from "https://deno.land/x/algebraic_enum/src/mod.ts"; + +class StatusVariants { + Success = Variant(); + Failure = Variant<{ code: number; message: string; }>(), - Pending: null, + Pending = null; }; -// Mark `Success` variant data type as generic -type Status = Enum; -``` - -Creating an enum factory is going to be more complicated, but possible. You can -wrap it in another function to specify the generic types. To avoid recreating -the enum factory over and over again, it is recommended to use the `memo` helper -function. +type Status = Enum>; +const Status = () => Enum.factory(StatusVariants); -```ts -type Status = Enum; -const Status = memo(() => Enum.factory>(StatusVariants)); - -let success = Status().Success("Hello World!"); -let failure = Status().Failure({ +const success = Status().Success("Hello World!"); +const failure = Status().Failure({ code: 404, message: "Not Found", }); -let pending = Status().Pending(null); +const pending = Status().Pending(); ``` ### Mutate Enum Variant By default, enum types are shallow read-only, meaning you can't change the variant of an existing enum value or assigning different data to an existing -variant (This is prevented by TypeScript's type system which doesn't incur +variant (this is prevented by TypeScript's type system which doesn't incur additional runtime performance penalty), but it's still possible to mutate the underlying variant data itself. -With `Enum.mutate`, you can change the variant of an existing enum value itself, -provided the variable is marked as mutable: - -```ts -import { Mut } from "https://deno.land/x/algebraic_enum/src/mod.ts"; - -// ... - -let status = Status().Success("Hello World!"); - -Enum.mutate(status, Status().Pending(null)); -// Compilation error, since `status` is not marked as mutable - -let mutableStatus = Status().Pending(null) as Mut>; - -Enum.mutate(mutableStatus, Status().Success("Mutated!")); -// `mutableStatus` is now a `Success` variant -``` - -### Enum Classes - -If you want to define methods on your enum for easier readability, you can -create an enum class which behaves like a normal enum and also like a class -where you can have instance methods. - -First, you need to define your enum methods separately, extending from the -abstract class `EnumImpl` along with your enum variants object. Your actual type -can be defined using the `EnumClass` helper type. - -Make sure your method and property names on your `EnumImpl` class do not collide -with your variant names. +With `Enum.mutate`, you can change the variant of an existing enum value itself: ```ts -import { - ofType, - memo, - Enum, - EnumClass, - EnumImpl, -} from "https://deno.land/x/algebraic_enum/src/mod.ts"; - -const StatusVariants = { - Success: ofType(), - Failure: ofType<{ - code: number; - message: string; - }>(), - Pending: null; -} - -class StatusImpl extends EnumImpl { - // Make sure to have the correct enum class type as `this`, otherwise you - // won't be able to treat `this` as an `Enum`. - getMessage(this: Status): string { - return Enum.match(this, { - Success: (data) => data, - Failure: (data) => data.message, - Pending: (data) => "Pending...", - }); - } - - // Declare `this` as mutable to enable enum mutation. - fail(this: Mut>, code: number, message: string): void { - Enum.mutate(this, Status().Failure({ code, message })); - } -} - -type Status = EnumClass>; -``` - -It's also possible to create an enum factory for easy object construction by -additionally passing `StatusImpl` to `Enum.factory`. - -```ts -const Status = memo(() => - Enum.factory>(StatusVariants, StatusImpl) -); - -let status = Status().Success("Hello!"); -let message = status.getMessage(); -message.fail(404, "Not found"); -// Compilation error, since `message` is not marked as mutable +const status = Status().Success("Hello World!"); -let mutableStatus = Status().Pending(null) as Mut>; -mutableStatus.fail(404, "Not found"); +Enum.mutate(status, Status().Pending()); +// `status` is now of variant `Pending` ``` diff --git a/src/enum.ts b/src/enum.ts index c180d5f..bb8c48c 100644 --- a/src/enum.ts +++ b/src/enum.ts @@ -1,62 +1,25 @@ -import type { EnumClassValue, EnumImpl } from "./enum_class.ts"; +type NoUndefined = Exclude; -declare const enumTag: unique symbol; - -export type NoUndefined = Exclude; - -export type EnumDefinition = Record & { _?: never }; - -export type DefinitionFromEnum> = NoUndefined< - E[typeof enumTag] ->["definition"]; - -export type EnumVariant> = - keyof DefinitionFromEnum; - -export type EnumValue< - E extends Enum, - V extends EnumVariant, -> = NoUndefined[V]>; - -export type EnumFactory> = { - [V in EnumVariant]: (data: EnumValue) => E; -}; - -export type ExhaustiveMatcher, T> = { - [V in EnumVariant]: (data: EnumValue) => T; -}; - -export type WildcardMatcher, T> = - & Partial> - & { _: () => T }; - -export type Matcher, T> = - | ExhaustiveMatcher - | WildcardMatcher; - -/** - * Marks an enum type as mutable, so it can be mutated by `Enum.mutate`. - */ -export type Mut> = E & { - readonly [enumTag]?: { mutable: true }; -}; +declare const enumDefinition: unique symbol; +declare const enumMutable: unique symbol; +const enumFactory = Symbol(); /** - * Create an enum type by defining all your variants in a separate object with - * the `ofType()` helper function. The data type contained in the variants + * Create an enum type by defining all your variants in a class with + * the `Variant()` function. The data type contained in the variants * cannot be `undefined`. The variant name cannot be `_` as it is reserved. * * Then use the helper type `Enum` to create your enum type. To construct enums * easier, you can use `Enum.factory()`. * * ```ts - * const MessageVariants = { - * Quit: null, - * Plaintext: ofType(), - * Encrypted: ofType(), + * class MessageVariants { + * Quit = null; + * Plaintext = Variant(); + * Encrypted = Variant(); * }; * - * type Message = Enum; + * type Message = Enum; * ``` * * It's also possible to create generic enum types. Mark any generic variants as @@ -64,204 +27,107 @@ export type Mut> = E & { * type definition itself: * * ```ts - * const MessageVariants = { - * Quit: null, - * Plaintext: ofType(), - * Encrypted: ofType(), + * class MessageVariants { + * Quit = null; + * Plaintext = Variant(); + * Encrypted = Variant(); * }; * - * type Message = Enum; + * type Message = Enum>; * ``` - * - * @template D Definition of all variants of the enum */ -export type Enum = - & { - [V in Exclude]: - & { readonly [_ in Exclude]?: never } - & { readonly [_ in V]: NoUndefined }; - }[Exclude] - & { - readonly [enumTag]?: { - definition: D; - mutable: unknown; - }; - }; +export type Enum = Readonly< + (unknown extends E ? {} + : { + [K in keyof E]: + & Record> + & Partial, never>>; + }[keyof E]) & { + [enumDefinition]?: E; + [enumMutable]?: boolean; + } +>; -function createEnumFactory>( - variants: Record, unknown>, -): EnumFactory; -function createEnumFactory< - I extends Enum & EnumImpl, ->( - variants: Record, unknown>, - Impl: new (value: EnumClassValue) => EnumImpl, -): EnumFactory; -function createEnumFactory>( - variants: Record, unknown>, - Impl?: new (value: unknown) => unknown, -) { - let result = {} as Record, any>; +type EnumDefinition> = NoUndefined< + T[typeof enumDefinition] +>; - for (let key in variants) { - let variant = key as EnumVariant; +type EnumFactory = { + [K in keyof E]: E[K] extends null ? () => Enum + : (value: NoUndefined) => Enum; +}; - result[variant] = Impl == null - ? (data: unknown = null) => ({ [variant]: data }) - : (data: unknown = null) => new Impl({ [variant]: data }); - } +type ExhaustiveMatcher = { + [K in keyof E]: (value: NoUndefined) => unknown; +}; - return result; +type WildcardMatcher = + & Partial> + & ExhaustiveMatcher<{ _: null }>; + +export type Matcher = ExhaustiveMatcher | WildcardMatcher; + +export function Variant(): T { + return undefined as never; +} + +export class NonExhaustiveMatcherError extends Error { + constructor() { + super( + "Non-exhaustive matcher. To ensure all possible cases are covered, you " + + "can add a wildcard `_` match arm.", + ); + } } -export const Enum = { - /** - * Creates easier constructors for the given enum type. - * - * ```ts - * const MessageVariants = { - * Quit: null, - * Plaintext: ofType(), - * Encrypted: ofType(), - * }; - * - * type Message = Enum; - * const Message = Enum.factory(MessageVariants); - * - * let plain = Message.Plaintext("Hello World!"); - * let quit = Message.Quit(null); - * ``` - * - * For generic enum types, you might want to use an additional function to - * provide the generic type. To avoid recreating the enum factory, use the - * `memo` helper function. - * - * ```ts - * const MessageVariants = { - * Quit: null, - * Plaintext: ofType(), - * Encrypted: ofType(), - * }; - * - * type Message = Enum; - * const Message = memo(() => Enum.factory>(MessageVariants)); - * - * let plain = Message().Plaintext("Hello World!"); - * let quit = Message().Quit(null); - * ``` - */ - factory: createEnumFactory, +export namespace Enum { + export function factory( + Enum: (new () => E) & { [enumFactory]?: EnumFactory }, + ): EnumFactory { + if (Enum[enumFactory] != null) return Enum[enumFactory]; - /** - * Inspects the given enum `value` and executes code based on which variant - * matches `value`. - * - * ```ts - * const MessageVariants = { - * Quit: null, - * Plaintext: ofType(), - * Encrypted: ofType(), - * }; - * - * type Message = Enum; - * const Message = Enum.factory(MessageVariants); - * - * let msg: Message = getMessage(); - * - * let length = Enum.match(msg, { - * Quit: () => -1, - * Plaintext: (data) => data.length, - * Encrypted: (data) => decrypt(data).length - * }); - * ``` - * - * Note that matches need to be exhaustive. You need to exhaust every last - * possibility in order for the code to be valid. The following code won't - * compile: - * - * ```ts - * Enum.match(msg, { - * Quit: () => console.log("Message stream ended.") - * }); - * ``` - * - * In case you don't care about other variants, you can either use the special - * wildcard match `_` which matches all variants not specified in the matcher, - * or a simple `if` statement: - * - * ```ts - * Enum.match(msg, { - * Quit: () => console.log("Message stream ended."), - * _: () => console.log("Stream goes on...") - * }); - * - * if (msg.Plaintext !== undefined) { - * console.log(msg.Plaintext); - * } - * ``` - * - * @param value The enum value to match against - * @param matcher - */ - match: , T>( - value: E, - matcher: Matcher, - ): T => { - let variant: keyof DefinitionFromEnum | "_" = "_"; + const result: Partial> = {}; + + for (const variant in new Enum()) { + result[variant] = ((value: any) => { + return { [variant]: value ?? null } as Enum; + }) as any; + } - for (let key in value) { - if (value[key] !== undefined && matcher[key] !== undefined) { - variant = key; - break; + return (Enum[enumFactory] = result as EnumFactory); + } + + export function match< + T extends Enum, + M extends Matcher>, + >( + value: T, + matcher: M, + ): { + [K in keyof M]: M[K] extends (arg: any) => any ? ReturnType : never; + }[keyof M] { + for (let variant in value) { + // @ts-ignore + if (value[variant] !== undefined && matcher[variant] != null) { + // @ts-ignore + return matcher[variant]!(value[variant]); } } - if (variant !== "_") { - return matcher[variant]!(value[variant as keyof E]); - } else if ("_" in matcher && matcher._ !== undefined) { - return (matcher as WildcardMatcher)._(); + if ("_" in matcher) { + // @ts-ignore + return matcher._(null); } - throw new Error( - "Non-exhaustive matcher. To ensure all possible cases are covered, you " + - "can add a wildcard `_` match arm.", - ); - }, + throw new NonExhaustiveMatcherError(); + } - /** - * Mutates the given `value` enum in-place to match the data in `other`. - * Requirement: The enum type has to be marked as mutable with `Mut`. - * - * ```ts - * const EVariants = { - * A: ofType(), - * B: ofType(), - * }; - * - * type E = Enum; - * const E = Enum.factory(EVariants); - * - * const a = E.A(5); - * Enum.mutate(a, E.B("Hello")); // Compilation error - * - * const b = E.A(5) as Mut; - * Enum.mutate(b, E.B("Hello")); - * - * console.log(b); - * // => { B: "Hello" } - * ``` - * - * @param value - * @param other - */ - mutate: ( - value: Mut>, - other: Enum, - ): void => { - for (let key in value) { - delete (value as any)[key]; + export function mutate>(value: T, other: T): void { + for (const variant in value) { + // @ts-ignore + delete value[variant]; } Object.assign(value, other); - }, -}; + } +} diff --git a/src/enum_class.ts b/src/enum_class.ts deleted file mode 100644 index 15dd615..0000000 --- a/src/enum_class.ts +++ /dev/null @@ -1,47 +0,0 @@ -import type { Enum, EnumDefinition, NoUndefined } from "./enum.ts"; - -const enumType = Symbol(); - -/** - * With `EnumImpl`, you can write your own classes that behave like enums. In - * particular, you can define methods that act on enums. - * - * ```ts - * const MessageVariants = { - * Quit: null, - * Plaintext: ofType(), - * Encrypted: ofType(), - * }; - * - * class MessageImpl extends EnumImpl { - * async send(this: Message): Promise { - * // ... - * } - * } - * - * type Message = EnumClass; - * const Message = Enum.factory(MessageVariants, MessageImpl); - * - * let msg = Message.Plaintext("Hello World!"); - * - * await Enum.match(msg, { - * Plaintext: async () => await msg.send(), - * _: async () => {} - * }); - * ``` - */ -export abstract class EnumImpl { - readonly [enumType]?: Enum; - - constructor(data: Enum) { - Object.assign(this, data); - } -} - -export type EnumClassValue> = NoUndefined< - I[typeof enumType] ->; - -export type EnumClass> = - & EnumClassValue - & I; diff --git a/src/mod.ts b/src/mod.ts index 1fd93a7..88a6647 100644 --- a/src/mod.ts +++ b/src/mod.ts @@ -1,7 +1 @@ -export { Enum } from "./enum.ts"; -export type { Mut } from "./enum.ts"; - -export { EnumImpl } from "./enum_class.ts"; -export type { EnumClass, EnumClassValue } from "./enum_class.ts"; - -export { memo, ofType } from "./utils.ts"; +export { Enum, Variant } from "./enum.ts"; diff --git a/src/result.ts b/src/result.ts deleted file mode 100644 index d1eefa1..0000000 --- a/src/result.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Enum, memo, ofType } from "./mod.ts"; - -const ResultVariants = { - Ok: ofType(), - Err: ofType(), -}; - -/** - * The `Result` enum type represents either success (`Ok` variant) or failure - * (`Err` variant). - * - * ```ts - * let a = Result().Ok(5); - * let b = Result().Err(new Error("Failed!")); - * ``` - * - * @template T Type of the data that the `Ok` variant contains - * @template E Type of the error that the `Err` variant contains - */ -export type Result = Enum< - typeof ResultVariants & { Ok: T; Err: E } ->; - -export const Result = memo(() => - Enum.factory>(ResultVariants) -); diff --git a/src/utils.ts b/src/utils.ts deleted file mode 100644 index 99a2d89..0000000 --- a/src/utils.ts +++ /dev/null @@ -1,10 +0,0 @@ -export const ofType = () => undefined as never as T; - -export function memo unknown>(f: F): F { - let cache: unknown | undefined; - - return (() => { - if (cache === undefined) cache = f(); - return cache; - }) as F; -} diff --git a/test/enum.test.ts b/test/enum.test.ts index e3bdc3a..af64d45 100644 --- a/test/enum.test.ts +++ b/test/enum.test.ts @@ -1,21 +1,21 @@ -import { Enum, Mut, ofType } from "../src/mod.ts"; -import { Matcher, NoUndefined } from "../src/enum.ts"; +import { Enum, Variant } from "../src/mod.ts"; +import { Matcher } from "../src/enum.ts"; import { assertEquals, assertThrows, expectType, TypeEqual, } from "../dev_deps.ts"; -import { TypeOf } from "./utils.ts"; +import { TypeExtends } from "./utils.ts"; -const EnumVariants = { - Quit: null, - Plaintext: ofType(), - Encrypted: ofType(), -}; +class MessageVariants { + Quit = null; + Plaintext = Variant(); + Encrypted = Variant(); +} -type Message = Enum; -const Message = Enum.factory(EnumVariants); +type Message = Enum; +const Message = () => Enum.factory(MessageVariants); Deno.test({ name: "Enums can contain one and only one variant", @@ -24,13 +24,16 @@ Deno.test({ expectType({ Plaintext: "Hello World!" }); expectType({ Encrypted: [4, 8, 15, 16, 23, 42] }); - expectType>(false); - expectType>(false); + expectType>(false); + expectType>(false); expectType< - TypeOf<{ - Quit: null; - Plaintext: ""; - }, Message> + TypeExtends< + { + Quit: null; + Plaintext: ""; + }, + Message + > >(false); }, }); @@ -38,13 +41,13 @@ Deno.test({ Deno.test({ name: "Enum factory should simplify enum value creation", fn() { - let msg = Message.Plaintext("Hello World"); + let msg = Message().Plaintext("Hello World"); assertEquals(msg, { Plaintext: "Hello World" }); - msg = Message.Quit(null); + msg = Message().Quit(); assertEquals(msg, { Quit: null }); - msg = Message.Encrypted([1, 2, 3]); + msg = Message().Encrypted([1, 2, 3]); assertEquals(msg, { Encrypted: [1, 2, 3] }); }, }); @@ -52,7 +55,7 @@ Deno.test({ Deno.test({ name: "Enum.match() has to be exhaustive", fn() { - type M = Matcher; + type M = Matcher; expectType({ Quit: () => -1, @@ -70,24 +73,33 @@ Deno.test({ _: () => 1, }); - expectType>(false); + expectType>(false); expectType< - TypeOf<{ - Quit: (data: NoUndefined) => number; - Plaintext: (data: NoUndefined) => number; - Encrypted: (data: NoUndefined) => number; - }, M> + TypeExtends< + { + Quit: () => number; + Plaintext: (data: string) => number; + Encrypted: (data: number[]) => number; + }, + M + > >(true); expectType< - TypeOf<{ - Plaintext: (data: NoUndefined) => number; - Encrypted: (data: NoUndefined) => number; - }, M> + TypeExtends< + { + Plaintext: (data: string) => number; + Encrypted: (data: number[]) => number; + }, + M + > >(false); expectType< - TypeOf<{ - Encrypted: (data: NoUndefined) => number; - }, M> + TypeExtends< + { + Encrypted: (data: number[]) => number; + }, + M + > >(false); }, }); @@ -96,7 +108,7 @@ Deno.test({ name: "Enum.match() should pick the right variant or wildcard", fn() { const run = (msg: Message) => - Enum.match(msg, { + Enum.match(msg, { Quit: () => -1, Plaintext: (data) => data.length, Encrypted: (data) => data.filter((x) => x !== 0).length, @@ -105,9 +117,9 @@ Deno.test({ expectType, number>>(true); assertThrows(() => run({} as any)); assertThrows(() => run({ Invalid: null } as any)); - assertEquals(run(Message.Quit(null)), -1); - assertEquals(run(Message.Plaintext("Hello!")), 6); - assertEquals(run(Message.Encrypted([0, 1, 2, 3, 0])), 3); + assertEquals(run(Message().Quit()), -1); + assertEquals(run(Message().Plaintext("Hello!")), 6); + assertEquals(run(Message().Encrypted([0, 1, 2, 3, 0])), 3); const run2 = (msg: Message) => Enum.match(msg, { @@ -118,23 +130,22 @@ Deno.test({ expectType, number>>(true); assertEquals(run2({} as any), -1); assertEquals(run2({ Invalid: null } as any), -1); - assertEquals(run2(Message.Quit(null)), -1); - assertEquals(run2(Message.Plaintext("Hello!")), 6); - assertEquals(run2(Message.Encrypted([0, 1, 2, 3, 0])), -1); + assertEquals(run2(Message().Quit()), -1); + assertEquals(run2(Message().Plaintext("Hello!")), 6); + assertEquals(run2(Message().Encrypted([0, 1, 2, 3, 0])), -1); }, }); Deno.test({ name: "Enum.mutate() should be able to change variant", fn() { - let msg = Message.Plaintext("Hello!") as Mut; - assertEquals(msg, Message.Plaintext("Hello!")); + const msg = Message().Plaintext("Hello!"); + assertEquals(msg, Message().Plaintext("Hello!")); - let other = Message.Encrypted([5, 4, 3]); + const other = Message().Encrypted([5, 4, 3]); Enum.mutate(msg, other); - assertEquals(msg, Message.Encrypted([5, 4, 3])); - assertEquals(other, Message.Encrypted([5, 4, 3])); - assertEquals(msg.Encrypted, other.Encrypted); + assertEquals(msg, Message().Encrypted([5, 4, 3])); + assertEquals(msg, other); }, }); diff --git a/test/enum_class.test.ts b/test/enum_class.test.ts deleted file mode 100644 index 9858a1b..0000000 --- a/test/enum_class.test.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { EnumDefinition, NoUndefined } from "../src/enum.ts"; -import { Enum, Mut } from "../src/mod.ts"; -import { EnumClass, EnumClassValue, EnumImpl } from "../src/enum_class.ts"; -import { assert, assertEquals, delay, expectType } from "../dev_deps.ts"; -import { TypeOf } from "./utils.ts"; -import { memo, ofType } from "../src/utils.ts"; - -const MessageVariants = { - Quit: null, - Plaintext: ofType(), - Encrypted: ofType(), -}; - -class MessageImpl extends EnumImpl< - typeof MessageVariants & { Plaintext: T } -> { - async send(): Promise { - await delay(100); - } - - encrypt(this: Mut>): void { - Enum.match(this, { - Plaintext: (data) => { - expectType>(data); - Enum.mutate(this, Message().Encrypted([1, 2, 3])); - }, - _: () => {}, - }); - } -} - -type Message = EnumClass>; -const Message = memo(() => - Enum.factory>(MessageVariants, MessageImpl) -); - -Deno.test({ - name: "EnumClass should be an Enum", - fn() { - let msg = Message().Quit(null); - type PureEnum = EnumClassValue>; - - expectType(msg); - expectType>>(true); - assertEquals(Object.keys(msg), ["Quit"]); - }, -}); - -Deno.test({ - name: "EnumClass should be filled with methods", - async fn() { - let msg = Message().Plaintext("Hello"); - await msg.send(); - }, -}); - -Deno.test({ - name: "EnumClass should interact well with Enum.match() and Enum.mutate()", - async fn() { - let msg = Message().Plaintext("Hello") as Mut>; - msg.encrypt(); - - assert( - Enum.match(msg, { - Encrypted: () => true, - _: () => false, - }), - "msg is Encrypted", - ); - - msg = Message().Quit(null) as Mut>; - msg.encrypt(); - - assert( - Enum.match(msg, { - Quit: () => true, - _: () => false, - }), - "msg is Quit", - ); - }, -}); diff --git a/test/utils.ts b/test/utils.ts index c430f7c..779dd5a 100644 --- a/test/utils.ts +++ b/test/utils.ts @@ -1 +1 @@ -export type TypeOf = T extends U ? true : false; +export type TypeExtends = T extends U ? true : false;