@anilkumarthakur/match
Advanced tools
+105
| /** | ||
| * Handler function type for match expression results | ||
| * | ||
| * A handler is a function that takes no parameters and returns a value | ||
| * of type T. Used in match expressions to define what happens when a case matches. | ||
| * | ||
| * @template T The return type of the handler function | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * const handler: Handler<string> = () => 'matched' | ||
| * const numHandler: Handler<number> = () => 42 | ||
| * ``` | ||
| */ | ||
| export declare type Handler<T> = () => T; | ||
| export declare function match<TSubject, TResult = unknown>(subject: TSubject): Matcher<TSubject, TResult>; | ||
| /** | ||
| * Interface representing a chainable match expression | ||
| * | ||
| * Provides the API contract for method chaining in match expressions. | ||
| * Implementations should support fluent interface patterns. | ||
| * | ||
| * @template TSubject The type of the value being matched against | ||
| * @template TResult The return type of handler functions | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * interface MatchChain<string, number> { | ||
| * on: (value: string, handler: Handler<number>) => MatchChain<string, number> | ||
| * otherwise: (handler: Handler<number>) => number | ||
| * } | ||
| * ``` | ||
| */ | ||
| export declare interface MatchChain<TSubject, TResult> { | ||
| /** | ||
| * Add a case to match against the subject | ||
| * | ||
| * @param {TSubject} value The value to match | ||
| * @param {Handler<TResult>} handler Function to execute if matched | ||
| * @returns {MatchChain<TSubject, TResult>} The matcher for chaining | ||
| */ | ||
| on: (value: TSubject, handler: Handler<TResult>) => MatchChain<TSubject, TResult>; | ||
| /** | ||
| * Set default handler and execute the match | ||
| * | ||
| * @param {Handler<TResult>} handler Function to execute if no cases match | ||
| * @returns {TResult} The result from matched handler or default | ||
| */ | ||
| otherwise: (handler: Handler<TResult>) => TResult; | ||
| } | ||
| /** | ||
| * Matcher class implementing PHP-style match expressions for TypeScript/JavaScript | ||
| * Supports exhaustive matching with type safety and O(1) lookup performance | ||
| */ | ||
| export declare class Matcher<TSubject, TResult> { | ||
| private readonly subject; | ||
| private readonly matches; | ||
| private defaultHandler?; | ||
| constructor(subject: TSubject); | ||
| on(value: TSubject, handler: Handler<TResult>): this; | ||
| onAny(values: readonly TSubject[], handler: Handler<TResult>): this; | ||
| otherwise(handler: Handler<TResult>): TResult; | ||
| default(handler: Handler<TResult>): TResult; | ||
| valueOf(): TResult; | ||
| private evaluate; | ||
| } | ||
| /** | ||
| * Alias for Handler<T> | ||
| * | ||
| * @template T The return type of the handler function | ||
| * @deprecated Use Handler<T> instead | ||
| */ | ||
| export declare type MatcherHandler<T> = Handler<T>; | ||
| /** | ||
| * Error thrown when a match expression has no matching case and no default handler | ||
| * | ||
| * @class UnhandledMatchError | ||
| * @extends Error | ||
| * | ||
| * @example | ||
| * try { | ||
| * match('foo') | ||
| * .on('bar', () => 'never matches') | ||
| * .valueOf() | ||
| * } catch (error) { | ||
| * if (error instanceof UnhandledMatchError) { | ||
| * console.error('No match found') | ||
| * } | ||
| * } | ||
| */ | ||
| export declare class UnhandledMatchError extends Error { | ||
| /** | ||
| * Create an UnhandledMatchError | ||
| * | ||
| * @param {unknown} value The value that could not be matched | ||
| */ | ||
| constructor(value: unknown); | ||
| } | ||
| export { } |
+0
-120
@@ -12,143 +12,23 @@ class a extends Error { | ||
| class h { | ||
| /** | ||
| * The value being matched against | ||
| * @private | ||
| */ | ||
| subject; | ||
| /** | ||
| * Map of values to their corresponding handler functions | ||
| * Uses Map for O(1) lookup performance | ||
| * @private | ||
| */ | ||
| matches = /* @__PURE__ */ new Map(); | ||
| /** | ||
| * Default handler to execute if no cases match | ||
| * @private | ||
| */ | ||
| defaultHandler; | ||
| /** | ||
| * Create a new Matcher instance | ||
| * | ||
| * @param {TSubject} subject The value to match against | ||
| * | ||
| * @internal Use the `match()` function instead of instantiating directly | ||
| */ | ||
| constructor(t) { | ||
| this.subject = t; | ||
| } | ||
| /** | ||
| * Add a case to match against the subject | ||
| * Uses strict equality (===) for comparison | ||
| * | ||
| * @param {TSubject} value The value to match against | ||
| * @param {MatcherHandler<TResult>} handler Function to execute if this value matches | ||
| * @returns {this} The matcher instance for method chaining | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * match('hello') | ||
| * .on('hello', () => 'matched') | ||
| * .on('goodbye', () => 'not matched') | ||
| * ``` | ||
| */ | ||
| on(t, s) { | ||
| return this.matches.set(t, s), this; | ||
| } | ||
| /** | ||
| * Add multiple values that map to the same handler | ||
| * Simulates PHP's comma-separated case syntax | ||
| * | ||
| * @param {readonly TSubject[]} values Array of values to match | ||
| * @param {MatcherHandler<TResult>} handler Function to execute if any value matches | ||
| * @returns {this} The matcher instance for method chaining | ||
| * | ||
| * @example HTTP Status Codes | ||
| * ```typescript | ||
| * match(statusCode) | ||
| * .onAny([200, 201, 202], () => 'Success') | ||
| * .onAny([400, 401, 403], () => 'Client Error') | ||
| * .otherwise(() => 'Unknown') | ||
| * ``` | ||
| * | ||
| * @see on For matching a single value | ||
| */ | ||
| onAny(t, s) { | ||
| return t.forEach((r) => this.matches.set(r, s)), this; | ||
| } | ||
| /** | ||
| * Set the default handler and execute the match expression | ||
| * This method triggers evaluation of all accumulated cases | ||
| * | ||
| * @param {MatcherHandler<TResult>} handler Function to execute if no cases match | ||
| * @returns {TResult} The result from the matched handler or the default handler | ||
| * @throws {UnhandledMatchError} If no case matches and no handler catches it | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * const result = match(status) | ||
| * .on('active', () => 'Active') | ||
| * .on('inactive', () => 'Inactive') | ||
| * .otherwise(() => 'Unknown status') | ||
| * ``` | ||
| * | ||
| * @see default For PHP-compatible alias | ||
| * @see valueOf For executing without a default handler | ||
| */ | ||
| otherwise(t) { | ||
| return this.defaultHandler = t, this.evaluate(); | ||
| } | ||
| /** | ||
| * PHP-compatible alias for otherwise() | ||
| * Identical behavior - sets default handler and executes | ||
| * | ||
| * @param {MatcherHandler<TResult>} handler Function to execute if no cases match | ||
| * @returns {TResult} The result from the matched handler or the default handler | ||
| * @throws {UnhandledMatchError} If no case matches | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * // PHP-style syntax | ||
| * const result = match(value) | ||
| * .on('case1', () => 'Result1') | ||
| * .default(() => 'Default') | ||
| * ``` | ||
| * | ||
| * @see otherwise For the standard method | ||
| */ | ||
| default(t) { | ||
| return this.otherwise(t); | ||
| } | ||
| /** | ||
| * Execute the match expression without a default handler | ||
| * Throws if no case matches | ||
| * | ||
| * @returns {TResult} The result from the matched handler | ||
| * @throws {UnhandledMatchError} If no case matches | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * try { | ||
| * const result = match(code) | ||
| * .on(200, () => 'OK') | ||
| * .on(404, () => 'Not Found') | ||
| * .valueOf() // Must have matched | ||
| * } catch (error) { | ||
| * if (error instanceof UnhandledMatchError) { | ||
| * console.error('Invalid code:', error.message) | ||
| * } | ||
| * } | ||
| * ``` | ||
| * | ||
| * @see otherwise For safe execution with default handler | ||
| */ | ||
| valueOf() { | ||
| return this.evaluate(); | ||
| } | ||
| /** | ||
| * Evaluate the match expression by finding the matching case | ||
| * | ||
| * @private | ||
| * @returns {TResult} The result from matched handler or default | ||
| * @throws {UnhandledMatchError} If no match and no default handler | ||
| */ | ||
| evaluate() { | ||
@@ -155,0 +35,0 @@ if (this.matches.has(this.subject)) |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.es.js","sources":["../src/Matcher.ts"],"sourcesContent":["import type { MatcherHandler } from './types/main'\n\n/**\n * Error thrown when a match expression has no matching case and no default handler\n *\n * @class UnhandledMatchError\n * @extends Error\n *\n * @example\n * try {\n * match('foo')\n * .on('bar', () => 'never matches')\n * .valueOf()\n * } catch (error) {\n * if (error instanceof UnhandledMatchError) {\n * console.error('No match found')\n * }\n * }\n */\nclass UnhandledMatchError extends Error {\n /**\n * Create an UnhandledMatchError\n *\n * @param {unknown} value The value that could not be matched\n */\n constructor(value: unknown) {\n super(`Unhandled match value: ${JSON.stringify(value)}`)\n this.name = 'UnhandledMatchError'\n }\n}\n\n/**\n * Matcher class implementing PHP-style match expressions for TypeScript/JavaScript\n * Supports exhaustive matching with type safety and O(1) lookup performance\n *\n * @template TSubject The type of values being matched against\n * @template TResult The return type of the match expression\n *\n * @class Matcher\n *\n * @example\n * ```typescript\n * const result = match('foo')\n * .on('foo', () => 'matched foo')\n * .on('bar', () => 'matched bar')\n * .otherwise(() => 'default')\n * ```\n *\n * @example HTTP Status Codes\n * ```typescript\n * const message = match(statusCode)\n * .on(200, () => 'OK')\n * .onAny([201, 202], () => 'Accepted')\n * .on(404, () => 'Not Found')\n * .otherwise(() => 'Unknown')\n * ```\n */\nclass Matcher<TSubject, TResult> {\n /**\n * The value being matched against\n * @private\n */\n private readonly subject: TSubject\n\n /**\n * Map of values to their corresponding handler functions\n * Uses Map for O(1) lookup performance\n * @private\n */\n private readonly matches: Map<TSubject, MatcherHandler<TResult>> = new Map()\n\n /**\n * Default handler to execute if no cases match\n * @private\n */\n private defaultHandler?: MatcherHandler<TResult>\n\n /**\n * Create a new Matcher instance\n *\n * @param {TSubject} subject The value to match against\n *\n * @internal Use the `match()` function instead of instantiating directly\n */\n constructor(subject: TSubject) {\n this.subject = subject\n }\n\n /**\n * Add a case to match against the subject\n * Uses strict equality (===) for comparison\n *\n * @param {TSubject} value The value to match against\n * @param {MatcherHandler<TResult>} handler Function to execute if this value matches\n * @returns {this} The matcher instance for method chaining\n *\n * @example\n * ```typescript\n * match('hello')\n * .on('hello', () => 'matched')\n * .on('goodbye', () => 'not matched')\n * ```\n */\n on(value: TSubject, handler: MatcherHandler<TResult>): this {\n this.matches.set(value, handler)\n return this\n }\n\n /**\n * Add multiple values that map to the same handler\n * Simulates PHP's comma-separated case syntax\n *\n * @param {readonly TSubject[]} values Array of values to match\n * @param {MatcherHandler<TResult>} handler Function to execute if any value matches\n * @returns {this} The matcher instance for method chaining\n *\n * @example HTTP Status Codes\n * ```typescript\n * match(statusCode)\n * .onAny([200, 201, 202], () => 'Success')\n * .onAny([400, 401, 403], () => 'Client Error')\n * .otherwise(() => 'Unknown')\n * ```\n *\n * @see on For matching a single value\n */\n onAny(values: readonly TSubject[], handler: MatcherHandler<TResult>): this {\n values.forEach((value) => this.matches.set(value, handler))\n return this\n }\n\n /**\n * Set the default handler and execute the match expression\n * This method triggers evaluation of all accumulated cases\n *\n * @param {MatcherHandler<TResult>} handler Function to execute if no cases match\n * @returns {TResult} The result from the matched handler or the default handler\n * @throws {UnhandledMatchError} If no case matches and no handler catches it\n *\n * @example\n * ```typescript\n * const result = match(status)\n * .on('active', () => 'Active')\n * .on('inactive', () => 'Inactive')\n * .otherwise(() => 'Unknown status')\n * ```\n *\n * @see default For PHP-compatible alias\n * @see valueOf For executing without a default handler\n */\n otherwise(handler: MatcherHandler<TResult>): TResult {\n this.defaultHandler = handler\n return this.evaluate()\n }\n\n /**\n * PHP-compatible alias for otherwise()\n * Identical behavior - sets default handler and executes\n *\n * @param {MatcherHandler<TResult>} handler Function to execute if no cases match\n * @returns {TResult} The result from the matched handler or the default handler\n * @throws {UnhandledMatchError} If no case matches\n *\n * @example\n * ```typescript\n * // PHP-style syntax\n * const result = match(value)\n * .on('case1', () => 'Result1')\n * .default(() => 'Default')\n * ```\n *\n * @see otherwise For the standard method\n */\n default(handler: MatcherHandler<TResult>): TResult {\n return this.otherwise(handler)\n }\n\n /**\n * Execute the match expression without a default handler\n * Throws if no case matches\n *\n * @returns {TResult} The result from the matched handler\n * @throws {UnhandledMatchError} If no case matches\n *\n * @example\n * ```typescript\n * try {\n * const result = match(code)\n * .on(200, () => 'OK')\n * .on(404, () => 'Not Found')\n * .valueOf() // Must have matched\n * } catch (error) {\n * if (error instanceof UnhandledMatchError) {\n * console.error('Invalid code:', error.message)\n * }\n * }\n * ```\n *\n * @see otherwise For safe execution with default handler\n */\n valueOf(): TResult {\n return this.evaluate()\n }\n\n /**\n * Evaluate the match expression by finding the matching case\n *\n * @private\n * @returns {TResult} The result from matched handler or default\n * @throws {UnhandledMatchError} If no match and no default handler\n */\n private evaluate(): TResult {\n if (this.matches.has(this.subject)) {\n return this.matches.get(this.subject)!()\n } else if (this.defaultHandler) {\n return this.defaultHandler()\n }\n throw new UnhandledMatchError(this.subject)\n }\n}\n\n/**\n * Create a new PHP-style match expression\n *\n * @template TSubject The type of the value being matched\n * @template TResult The return type of the match expression handlers\n *\n * @param {TSubject} subject The value to match against (any type)\n * @returns {Matcher<TSubject, TResult>} A Matcher instance for method chaining\n *\n * @example Basic String Matching\n * ```typescript\n * const status = match(statusCode)\n * .on(200, () => 'success')\n * .on(404, () => 'not found')\n * .otherwise(() => 'error')\n * ```\n *\n * @example HTTP Status Codes\n * ```typescript\n * const message = match(code)\n * .onAny([200, 201, 202], () => 'Success')\n * .onAny([400, 401, 403], () => 'Client Error')\n * .on(500, () => 'Server Error')\n * .otherwise(() => 'Unknown')\n * ```\n *\n * @example Conditional Logic\n * ```typescript\n * const result = match(true)\n * .on(age < 18, () => 'Minor')\n * .on(age >= 18 && age < 65, () => 'Adult')\n * .on(age >= 65, () => 'Senior')\n * .otherwise(() => 'Unknown')\n * ```\n *\n * @see Matcher For complete API documentation\n */\nfunction match<TSubject, TResult>(subject: TSubject): Matcher<TSubject, TResult> {\n return new Matcher<TSubject, TResult>(subject)\n}\n\nexport { match, UnhandledMatchError, Matcher }\n"],"names":["UnhandledMatchError","value","Matcher","subject","handler","values","match"],"mappings":"AAmBA,MAAMA,UAA4B,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMtC,YAAYC,GAAgB;AAC1B,UAAM,0BAA0B,KAAK,UAAUA,CAAK,CAAC,EAAE,GACvD,KAAK,OAAO;AAAA,EACd;AACF;AA4BA,MAAMC,EAA2B;AAAA;AAAA;AAAA;AAAA;AAAA,EAKd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,8BAAsD,IAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAM/D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASR,YAAYC,GAAmB;AAC7B,SAAK,UAAUA;AAAA,EACjB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBA,GAAGF,GAAiBG,GAAwC;AAC1D,gBAAK,QAAQ,IAAIH,GAAOG,CAAO,GACxB;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAoBA,MAAMC,GAA6BD,GAAwC;AACzE,WAAAC,EAAO,QAAQ,CAACJ,MAAU,KAAK,QAAQ,IAAIA,GAAOG,CAAO,CAAC,GACnD;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAqBA,UAAUA,GAA2C;AACnD,gBAAK,iBAAiBA,GACf,KAAK,SAAA;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAoBA,QAAQA,GAA2C;AACjD,WAAO,KAAK,UAAUA,CAAO;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAyBA,UAAmB;AACjB,WAAO,KAAK,SAAA;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASQ,WAAoB;AAC1B,QAAI,KAAK,QAAQ,IAAI,KAAK,OAAO;AAC/B,aAAO,KAAK,QAAQ,IAAI,KAAK,OAAO,EAAA;AACtC,QAAW,KAAK;AACd,aAAO,KAAK,eAAA;AAEd,UAAM,IAAIJ,EAAoB,KAAK,OAAO;AAAA,EAC5C;AACF;AAuCA,SAASM,EAAyBH,GAA+C;AAC/E,SAAO,IAAID,EAA2BC,CAAO;AAC/C;"} | ||
| {"version":3,"file":"index.es.js","sources":["../src/errors.ts","../src/matcher.ts"],"sourcesContent":["/**\n * Error thrown when a match expression has no matching case and no default handler\n *\n * @class UnhandledMatchError\n * @extends Error\n *\n * @example\n * try {\n * match('foo')\n * .on('bar', () => 'never matches')\n * .valueOf()\n * } catch (error) {\n * if (error instanceof UnhandledMatchError) {\n * console.error('No match found')\n * }\n * }\n */\nexport class UnhandledMatchError extends Error {\n /**\n * Create an UnhandledMatchError\n *\n * @param {unknown} value The value that could not be matched\n */\n constructor(value: unknown) {\n super(`Unhandled match value: ${JSON.stringify(value)}`)\n this.name = 'UnhandledMatchError'\n }\n}\n","import type { Handler } from './types'\nimport { UnhandledMatchError } from './errors'\n\n/**\n * Matcher class implementing PHP-style match expressions for TypeScript/JavaScript\n * Supports exhaustive matching with type safety and O(1) lookup performance\n */\nexport class Matcher<TSubject, TResult> {\n private readonly subject: TSubject\n private readonly matches: Map<TSubject, Handler<TResult>> = new Map()\n private defaultHandler?: Handler<TResult>\n\n constructor(subject: TSubject) {\n this.subject = subject\n }\n\n on(value: TSubject, handler: Handler<TResult>): this {\n this.matches.set(value, handler)\n return this\n }\n\n onAny(values: readonly TSubject[], handler: Handler<TResult>): this {\n values.forEach((value) => this.matches.set(value, handler))\n return this\n }\n\n otherwise(handler: Handler<TResult>): TResult {\n this.defaultHandler = handler\n return this.evaluate()\n }\n\n default(handler: Handler<TResult>): TResult {\n return this.otherwise(handler)\n }\n\n valueOf(): TResult {\n return this.evaluate()\n }\n\n private evaluate(): TResult {\n if (this.matches.has(this.subject)) {\n return this.matches.get(this.subject)!()\n } else if (this.defaultHandler) {\n return this.defaultHandler()\n }\n throw new UnhandledMatchError(this.subject)\n }\n}\n\nexport function match<TSubject, TResult = unknown>(subject: TSubject): Matcher<TSubject, TResult> {\n return new Matcher<TSubject, TResult>(subject)\n}\n"],"names":["UnhandledMatchError","value","Matcher","subject","handler","values","match"],"mappings":"AAiBO,MAAMA,UAA4B,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAM7C,YAAYC,GAAgB;AAC1B,UAAM,0BAA0B,KAAK,UAAUA,CAAK,CAAC,EAAE,GACvD,KAAK,OAAO;AAAA,EACd;AACF;ACpBO,MAAMC,EAA2B;AAAA,EACrB;AAAA,EACA,8BAA+C,IAAA;AAAA,EACxD;AAAA,EAER,YAAYC,GAAmB;AAC7B,SAAK,UAAUA;AAAA,EACjB;AAAA,EAEA,GAAGF,GAAiBG,GAAiC;AACnD,gBAAK,QAAQ,IAAIH,GAAOG,CAAO,GACxB;AAAA,EACT;AAAA,EAEA,MAAMC,GAA6BD,GAAiC;AAClE,WAAAC,EAAO,QAAQ,CAACJ,MAAU,KAAK,QAAQ,IAAIA,GAAOG,CAAO,CAAC,GACnD;AAAA,EACT;AAAA,EAEA,UAAUA,GAAoC;AAC5C,gBAAK,iBAAiBA,GACf,KAAK,SAAA;AAAA,EACd;AAAA,EAEA,QAAQA,GAAoC;AAC1C,WAAO,KAAK,UAAUA,CAAO;AAAA,EAC/B;AAAA,EAEA,UAAmB;AACjB,WAAO,KAAK,SAAA;AAAA,EACd;AAAA,EAEQ,WAAoB;AAC1B,QAAI,KAAK,QAAQ,IAAI,KAAK,OAAO;AAC/B,aAAO,KAAK,QAAQ,IAAI,KAAK,OAAO,EAAA;AACtC,QAAW,KAAK;AACd,aAAO,KAAK,eAAA;AAEd,UAAM,IAAIJ,EAAoB,KAAK,OAAO;AAAA,EAC5C;AACF;AAEO,SAASM,EAAmCH,GAA+C;AAChG,SAAO,IAAID,EAA2BC,CAAO;AAC/C;"} |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.umd.js","sources":["../src/Matcher.ts"],"sourcesContent":["import type { MatcherHandler } from './types/main'\n\n/**\n * Error thrown when a match expression has no matching case and no default handler\n *\n * @class UnhandledMatchError\n * @extends Error\n *\n * @example\n * try {\n * match('foo')\n * .on('bar', () => 'never matches')\n * .valueOf()\n * } catch (error) {\n * if (error instanceof UnhandledMatchError) {\n * console.error('No match found')\n * }\n * }\n */\nclass UnhandledMatchError extends Error {\n /**\n * Create an UnhandledMatchError\n *\n * @param {unknown} value The value that could not be matched\n */\n constructor(value: unknown) {\n super(`Unhandled match value: ${JSON.stringify(value)}`)\n this.name = 'UnhandledMatchError'\n }\n}\n\n/**\n * Matcher class implementing PHP-style match expressions for TypeScript/JavaScript\n * Supports exhaustive matching with type safety and O(1) lookup performance\n *\n * @template TSubject The type of values being matched against\n * @template TResult The return type of the match expression\n *\n * @class Matcher\n *\n * @example\n * ```typescript\n * const result = match('foo')\n * .on('foo', () => 'matched foo')\n * .on('bar', () => 'matched bar')\n * .otherwise(() => 'default')\n * ```\n *\n * @example HTTP Status Codes\n * ```typescript\n * const message = match(statusCode)\n * .on(200, () => 'OK')\n * .onAny([201, 202], () => 'Accepted')\n * .on(404, () => 'Not Found')\n * .otherwise(() => 'Unknown')\n * ```\n */\nclass Matcher<TSubject, TResult> {\n /**\n * The value being matched against\n * @private\n */\n private readonly subject: TSubject\n\n /**\n * Map of values to their corresponding handler functions\n * Uses Map for O(1) lookup performance\n * @private\n */\n private readonly matches: Map<TSubject, MatcherHandler<TResult>> = new Map()\n\n /**\n * Default handler to execute if no cases match\n * @private\n */\n private defaultHandler?: MatcherHandler<TResult>\n\n /**\n * Create a new Matcher instance\n *\n * @param {TSubject} subject The value to match against\n *\n * @internal Use the `match()` function instead of instantiating directly\n */\n constructor(subject: TSubject) {\n this.subject = subject\n }\n\n /**\n * Add a case to match against the subject\n * Uses strict equality (===) for comparison\n *\n * @param {TSubject} value The value to match against\n * @param {MatcherHandler<TResult>} handler Function to execute if this value matches\n * @returns {this} The matcher instance for method chaining\n *\n * @example\n * ```typescript\n * match('hello')\n * .on('hello', () => 'matched')\n * .on('goodbye', () => 'not matched')\n * ```\n */\n on(value: TSubject, handler: MatcherHandler<TResult>): this {\n this.matches.set(value, handler)\n return this\n }\n\n /**\n * Add multiple values that map to the same handler\n * Simulates PHP's comma-separated case syntax\n *\n * @param {readonly TSubject[]} values Array of values to match\n * @param {MatcherHandler<TResult>} handler Function to execute if any value matches\n * @returns {this} The matcher instance for method chaining\n *\n * @example HTTP Status Codes\n * ```typescript\n * match(statusCode)\n * .onAny([200, 201, 202], () => 'Success')\n * .onAny([400, 401, 403], () => 'Client Error')\n * .otherwise(() => 'Unknown')\n * ```\n *\n * @see on For matching a single value\n */\n onAny(values: readonly TSubject[], handler: MatcherHandler<TResult>): this {\n values.forEach((value) => this.matches.set(value, handler))\n return this\n }\n\n /**\n * Set the default handler and execute the match expression\n * This method triggers evaluation of all accumulated cases\n *\n * @param {MatcherHandler<TResult>} handler Function to execute if no cases match\n * @returns {TResult} The result from the matched handler or the default handler\n * @throws {UnhandledMatchError} If no case matches and no handler catches it\n *\n * @example\n * ```typescript\n * const result = match(status)\n * .on('active', () => 'Active')\n * .on('inactive', () => 'Inactive')\n * .otherwise(() => 'Unknown status')\n * ```\n *\n * @see default For PHP-compatible alias\n * @see valueOf For executing without a default handler\n */\n otherwise(handler: MatcherHandler<TResult>): TResult {\n this.defaultHandler = handler\n return this.evaluate()\n }\n\n /**\n * PHP-compatible alias for otherwise()\n * Identical behavior - sets default handler and executes\n *\n * @param {MatcherHandler<TResult>} handler Function to execute if no cases match\n * @returns {TResult} The result from the matched handler or the default handler\n * @throws {UnhandledMatchError} If no case matches\n *\n * @example\n * ```typescript\n * // PHP-style syntax\n * const result = match(value)\n * .on('case1', () => 'Result1')\n * .default(() => 'Default')\n * ```\n *\n * @see otherwise For the standard method\n */\n default(handler: MatcherHandler<TResult>): TResult {\n return this.otherwise(handler)\n }\n\n /**\n * Execute the match expression without a default handler\n * Throws if no case matches\n *\n * @returns {TResult} The result from the matched handler\n * @throws {UnhandledMatchError} If no case matches\n *\n * @example\n * ```typescript\n * try {\n * const result = match(code)\n * .on(200, () => 'OK')\n * .on(404, () => 'Not Found')\n * .valueOf() // Must have matched\n * } catch (error) {\n * if (error instanceof UnhandledMatchError) {\n * console.error('Invalid code:', error.message)\n * }\n * }\n * ```\n *\n * @see otherwise For safe execution with default handler\n */\n valueOf(): TResult {\n return this.evaluate()\n }\n\n /**\n * Evaluate the match expression by finding the matching case\n *\n * @private\n * @returns {TResult} The result from matched handler or default\n * @throws {UnhandledMatchError} If no match and no default handler\n */\n private evaluate(): TResult {\n if (this.matches.has(this.subject)) {\n return this.matches.get(this.subject)!()\n } else if (this.defaultHandler) {\n return this.defaultHandler()\n }\n throw new UnhandledMatchError(this.subject)\n }\n}\n\n/**\n * Create a new PHP-style match expression\n *\n * @template TSubject The type of the value being matched\n * @template TResult The return type of the match expression handlers\n *\n * @param {TSubject} subject The value to match against (any type)\n * @returns {Matcher<TSubject, TResult>} A Matcher instance for method chaining\n *\n * @example Basic String Matching\n * ```typescript\n * const status = match(statusCode)\n * .on(200, () => 'success')\n * .on(404, () => 'not found')\n * .otherwise(() => 'error')\n * ```\n *\n * @example HTTP Status Codes\n * ```typescript\n * const message = match(code)\n * .onAny([200, 201, 202], () => 'Success')\n * .onAny([400, 401, 403], () => 'Client Error')\n * .on(500, () => 'Server Error')\n * .otherwise(() => 'Unknown')\n * ```\n *\n * @example Conditional Logic\n * ```typescript\n * const result = match(true)\n * .on(age < 18, () => 'Minor')\n * .on(age >= 18 && age < 65, () => 'Adult')\n * .on(age >= 65, () => 'Senior')\n * .otherwise(() => 'Unknown')\n * ```\n *\n * @see Matcher For complete API documentation\n */\nfunction match<TSubject, TResult>(subject: TSubject): Matcher<TSubject, TResult> {\n return new Matcher<TSubject, TResult>(subject)\n}\n\nexport { match, UnhandledMatchError, Matcher }\n"],"names":["UnhandledMatchError","value","Matcher","subject","handler","values","match"],"mappings":"8NAmBA,MAAMA,UAA4B,KAAM,CAMtC,YAAYC,EAAgB,CAC1B,MAAM,0BAA0B,KAAK,UAAUA,CAAK,CAAC,EAAE,EACvD,KAAK,KAAO,qBACd,CACF,CA4BA,MAAMC,CAA2B,CAKd,QAOA,YAAsD,IAM/D,eASR,YAAYC,EAAmB,CAC7B,KAAK,QAAUA,CACjB,CAiBA,GAAGF,EAAiBG,EAAwC,CAC1D,YAAK,QAAQ,IAAIH,EAAOG,CAAO,EACxB,IACT,CAoBA,MAAMC,EAA6BD,EAAwC,CACzE,OAAAC,EAAO,QAASJ,GAAU,KAAK,QAAQ,IAAIA,EAAOG,CAAO,CAAC,EACnD,IACT,CAqBA,UAAUA,EAA2C,CACnD,YAAK,eAAiBA,EACf,KAAK,SAAA,CACd,CAoBA,QAAQA,EAA2C,CACjD,OAAO,KAAK,UAAUA,CAAO,CAC/B,CAyBA,SAAmB,CACjB,OAAO,KAAK,SAAA,CACd,CASQ,UAAoB,CAC1B,GAAI,KAAK,QAAQ,IAAI,KAAK,OAAO,EAC/B,OAAO,KAAK,QAAQ,IAAI,KAAK,OAAO,EAAA,EACtC,GAAW,KAAK,eACd,OAAO,KAAK,eAAA,EAEd,MAAM,IAAIJ,EAAoB,KAAK,OAAO,CAC5C,CACF,CAuCA,SAASM,EAAyBH,EAA+C,CAC/E,OAAO,IAAID,EAA2BC,CAAO,CAC/C"} | ||
| {"version":3,"file":"index.umd.js","sources":["../src/errors.ts","../src/matcher.ts"],"sourcesContent":["/**\n * Error thrown when a match expression has no matching case and no default handler\n *\n * @class UnhandledMatchError\n * @extends Error\n *\n * @example\n * try {\n * match('foo')\n * .on('bar', () => 'never matches')\n * .valueOf()\n * } catch (error) {\n * if (error instanceof UnhandledMatchError) {\n * console.error('No match found')\n * }\n * }\n */\nexport class UnhandledMatchError extends Error {\n /**\n * Create an UnhandledMatchError\n *\n * @param {unknown} value The value that could not be matched\n */\n constructor(value: unknown) {\n super(`Unhandled match value: ${JSON.stringify(value)}`)\n this.name = 'UnhandledMatchError'\n }\n}\n","import type { Handler } from './types'\nimport { UnhandledMatchError } from './errors'\n\n/**\n * Matcher class implementing PHP-style match expressions for TypeScript/JavaScript\n * Supports exhaustive matching with type safety and O(1) lookup performance\n */\nexport class Matcher<TSubject, TResult> {\n private readonly subject: TSubject\n private readonly matches: Map<TSubject, Handler<TResult>> = new Map()\n private defaultHandler?: Handler<TResult>\n\n constructor(subject: TSubject) {\n this.subject = subject\n }\n\n on(value: TSubject, handler: Handler<TResult>): this {\n this.matches.set(value, handler)\n return this\n }\n\n onAny(values: readonly TSubject[], handler: Handler<TResult>): this {\n values.forEach((value) => this.matches.set(value, handler))\n return this\n }\n\n otherwise(handler: Handler<TResult>): TResult {\n this.defaultHandler = handler\n return this.evaluate()\n }\n\n default(handler: Handler<TResult>): TResult {\n return this.otherwise(handler)\n }\n\n valueOf(): TResult {\n return this.evaluate()\n }\n\n private evaluate(): TResult {\n if (this.matches.has(this.subject)) {\n return this.matches.get(this.subject)!()\n } else if (this.defaultHandler) {\n return this.defaultHandler()\n }\n throw new UnhandledMatchError(this.subject)\n }\n}\n\nexport function match<TSubject, TResult = unknown>(subject: TSubject): Matcher<TSubject, TResult> {\n return new Matcher<TSubject, TResult>(subject)\n}\n"],"names":["UnhandledMatchError","value","Matcher","subject","handler","values","match"],"mappings":"8NAiBO,MAAMA,UAA4B,KAAM,CAM7C,YAAYC,EAAgB,CAC1B,MAAM,0BAA0B,KAAK,UAAUA,CAAK,CAAC,EAAE,EACvD,KAAK,KAAO,qBACd,CACF,CCpBO,MAAMC,CAA2B,CACrB,QACA,YAA+C,IACxD,eAER,YAAYC,EAAmB,CAC7B,KAAK,QAAUA,CACjB,CAEA,GAAGF,EAAiBG,EAAiC,CACnD,YAAK,QAAQ,IAAIH,EAAOG,CAAO,EACxB,IACT,CAEA,MAAMC,EAA6BD,EAAiC,CAClE,OAAAC,EAAO,QAASJ,GAAU,KAAK,QAAQ,IAAIA,EAAOG,CAAO,CAAC,EACnD,IACT,CAEA,UAAUA,EAAoC,CAC5C,YAAK,eAAiBA,EACf,KAAK,SAAA,CACd,CAEA,QAAQA,EAAoC,CAC1C,OAAO,KAAK,UAAUA,CAAO,CAC/B,CAEA,SAAmB,CACjB,OAAO,KAAK,SAAA,CACd,CAEQ,UAAoB,CAC1B,GAAI,KAAK,QAAQ,IAAI,KAAK,OAAO,EAC/B,OAAO,KAAK,QAAQ,IAAI,KAAK,OAAO,EAAA,EACtC,GAAW,KAAK,eACd,OAAO,KAAK,eAAA,EAEd,MAAM,IAAIJ,EAAoB,KAAK,OAAO,CAC5C,CACF,CAEO,SAASM,EAAmCH,EAA+C,CAChG,OAAO,IAAID,EAA2BC,CAAO,CAC/C"} |
+1
-1
@@ -5,3 +5,3 @@ { | ||
| "private": false, | ||
| "version": "0.0.8", | ||
| "version": "0.1.1", | ||
| "type": "module", | ||
@@ -8,0 +8,0 @@ "license": "MIT", |
+5
-5
@@ -5,3 +5,3 @@ # @anilkumarthakur/match | ||
| [](LICENSE) | ||
| [](test/) | ||
| [](test/) | ||
| [](#) | ||
@@ -18,4 +18,4 @@ | ||
| 🚀 **Fast**: Efficient equality-based matching using JavaScript's Map | ||
| 📦 **Lightweight**: Zero dependencies, ~1.2KB gzipped | ||
| 🧪 **Well-Tested**: 245 comprehensive tests with 100% code coverage | ||
| 📦 **Lightweight**: Zero dependencies, only ~0.5KB gzipped (ES module) | ||
| 🧪 **Well-Tested**: 127 comprehensive tests with 100% code coverage | ||
| 🔗 **Chainable**: Fluent API for method chaining | ||
@@ -389,7 +389,7 @@ 🌍 **Cross-Platform**: Works in Node.js and browsers (ESM + UMD) | ||
| - 💾 Lazy evaluation - only matched handler executes | ||
| - 📦 ~1.2KB gzipped bundle size | ||
| - 📦 Bundle sizes: ES (1.03 kB / 0.46 kB gzipped), UMD (0.99 kB / 0.51 kB gzipped) | ||
| ## Testing | ||
| The library includes 245 comprehensive tests with 100% code coverage: | ||
| The library includes 127 comprehensive tests with 100% code coverage: | ||
@@ -396,0 +396,0 @@ ```bash |
@@ -1,2 +0,3 @@ | ||
| import { match, UnhandledMatchError, Matcher } from '../src/Matcher' | ||
| import { match, Matcher } from '../src/matcher' | ||
| import { UnhandledMatchError } from '../src/errors' | ||
@@ -1073,2 +1074,50 @@ beforeEach(() => { | ||
| }) | ||
| test('typed handleCheck example', () => { | ||
| type CheckResult = { ok: boolean; code: number; warn?: boolean } | ||
| const handleCheck = (check: string): CheckResult => { | ||
| return match<string, CheckResult>(check) | ||
| .on('error', () => ({ ok: false, code: 500 })) | ||
| .on('warn', () => ({ ok: true, code: 200, warn: true })) | ||
| .on('ok', () => ({ ok: true, code: 200 })) | ||
| .otherwise(() => ({ ok: false, code: 400 })) | ||
| } | ||
| const errorResult = handleCheck('error') | ||
| expect(errorResult.ok).toBe(false) | ||
| expect(errorResult.code).toBe(500) | ||
| const warnResult = handleCheck('warn') | ||
| expect(warnResult.ok).toBe(true) | ||
| expect(warnResult.warn).toBe(true) | ||
| const okResult = handleCheck('ok') | ||
| expect(okResult.ok).toBe(true) | ||
| expect(okResult.code).toBe(200) | ||
| const unknownResult = handleCheck('unknown') | ||
| expect(unknownResult.ok).toBe(false) | ||
| expect(unknownResult.code).toBe(400) | ||
| }) | ||
| test('type safety with object return type', () => { | ||
| interface ApiResponse { | ||
| status: string | ||
| data?: unknown | ||
| error?: string | ||
| } | ||
| const getResponse = (code: number): ApiResponse => { | ||
| return match<number, ApiResponse>(code) | ||
| .on(200, () => ({ status: 'success', data: {} })) | ||
| .on(404, () => ({ status: 'error', error: 'Not found' })) | ||
| .on(500, () => ({ status: 'error', error: 'Server error' })) | ||
| .otherwise(() => ({ status: 'unknown' })) | ||
| } | ||
| expect(getResponse(200).status).toBe('success') | ||
| expect(getResponse(404).error).toBe('Not found') | ||
| expect(getResponse(999).status).toBe('unknown') | ||
| }) | ||
| }) | ||
@@ -1146,9 +1195,12 @@ | ||
| test('FizzBuzz example', () => { | ||
| test('FizzBuzz example using modulo matching', () => { | ||
| // Note: This pattern uses match(0) to check if remainder is 0 | ||
| // Order matters - more specific checks must come last since Map overwrites | ||
| const fizzbuzz = (num: number) => | ||
| match(0) | ||
| .on(num % 15, () => 'FizzBuzz') | ||
| .on(num % 3, () => 'Fizz') | ||
| .on(num % 5, () => 'Buzz') | ||
| match(num % 15 === 0 ? 'fizzbuzz' : num % 3 === 0 ? 'fizz' : num % 5 === 0 ? 'buzz' : 'num') | ||
| .on('fizzbuzz', () => 'FizzBuzz') | ||
| .on('fizz', () => 'Fizz') | ||
| .on('buzz', () => 'Buzz') | ||
| .otherwise(() => num.toString()) | ||
| expect(fizzbuzz(15)).toBe('FizzBuzz') | ||
| expect(fizzbuzz(3)).toBe('Fizz') | ||
@@ -1159,2 +1211,38 @@ expect(fizzbuzz(5)).toBe('Buzz') | ||
| test('FizzBuzz with conditional match(true)', () => { | ||
| // When using match(true), only one condition should be true at a time | ||
| // Use early return pattern or most specific check first | ||
| const fizzBuzz = (n: number): string => { | ||
| // Check most specific first | ||
| if (n % 15 === 0) { | ||
| return match<boolean, string>(true) | ||
| .on(true, () => 'FizzBuzz') | ||
| .otherwise(() => '') | ||
| } | ||
| return match<boolean, string>(true) | ||
| .on(n % 3 === 0, () => 'Fizz') | ||
| .on(n % 5 === 0, () => 'Buzz') | ||
| .otherwise(() => n.toString()) | ||
| } | ||
| expect(fizzBuzz(15)).toBe('FizzBuzz') | ||
| expect(fizzBuzz(9)).toBe('Fizz') | ||
| expect(fizzBuzz(5)).toBe('Buzz') | ||
| expect(fizzBuzz(7)).toBe('7') | ||
| }) | ||
| test('FizzBuzz with simple if-else (for comparison)', () => { | ||
| const fizzBuzz = (n: number): string => { | ||
| if (n % 15 === 0) return 'FizzBuzz' | ||
| if (n % 3 === 0) return 'Fizz' | ||
| if (n % 5 === 0) return 'Buzz' | ||
| return n.toString() | ||
| } | ||
| expect(fizzBuzz(15)).toBe('FizzBuzz') | ||
| expect(fizzBuzz(9)).toBe('Fizz') | ||
| expect(fizzBuzz(5)).toBe('Buzz') | ||
| expect(fizzBuzz(7)).toBe('7') | ||
| }) | ||
| test('days in month example', () => { | ||
@@ -1161,0 +1249,0 @@ const isLeap = (year: number) => year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0) |
| import { match } from './match'; | ||
| /** | ||
| * Create a new match expression | ||
| * | ||
| * @template TSubject The type of the value being matched | ||
| * @template TResult The return type of handler functions | ||
| * | ||
| * @param {TSubject} subject The value to match against | ||
| * @returns {Matcher<TSubject, TResult>} A Matcher instance | ||
| * | ||
| * @see {@link Matcher} For complete API documentation | ||
| */ | ||
| export { match }; | ||
| /** | ||
| * Core classes and types for the match expression library | ||
| */ | ||
| export { Matcher, UnhandledMatchError } from './Matcher'; | ||
| /** | ||
| * Type for match handler functions | ||
| * | ||
| * @template T The return type of the handler | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * const handler: Handler<string> = () => 'result' | ||
| * ``` | ||
| */ | ||
| export type { Handler, MatchChain, MatcherHandler } from './types/main'; | ||
| //# sourceMappingURL=index.d.ts.map |
| {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAE/B;;;;;;;;;;GAUG;AACH,OAAO,EAAE,KAAK,EAAE,CAAA;AAEhB;;GAEG;AACH,OAAO,EAAE,OAAO,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAA;AAExD;;;;;;;;;GASG;AACH,YAAY,EAAE,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA"} |
| /** | ||
| * Re-export module for backwards compatibility | ||
| * | ||
| * This module maintains backwards compatibility by re-exporting | ||
| * from the main Matcher module. New code should import from | ||
| * the root index.ts or directly from Matcher.ts | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * // Backwards compatible (legacy) | ||
| * import { match } from './match' | ||
| * | ||
| * // Recommended | ||
| * import { match } from '@anilkumarthakur/match' | ||
| * ``` | ||
| */ | ||
| export { match } from './Matcher'; | ||
| /** | ||
| * Re-export all types from the types module | ||
| */ | ||
| export type { MatchChain, Handler, MatcherHandler } from './types/main'; | ||
| //# sourceMappingURL=match.d.ts.map |
| {"version":3,"file":"match.d.ts","sourceRoot":"","sources":["../../src/match.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAE,KAAK,EAAE,MAAM,WAAW,CAAA;AAEjC;;GAEG;AACH,YAAY,EAAE,UAAU,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA"} |
| import { MatcherHandler } from './types/main'; | ||
| /** | ||
| * Error thrown when a match expression has no matching case and no default handler | ||
| * | ||
| * @class UnhandledMatchError | ||
| * @extends Error | ||
| * | ||
| * @example | ||
| * try { | ||
| * match('foo') | ||
| * .on('bar', () => 'never matches') | ||
| * .valueOf() | ||
| * } catch (error) { | ||
| * if (error instanceof UnhandledMatchError) { | ||
| * console.error('No match found') | ||
| * } | ||
| * } | ||
| */ | ||
| declare class UnhandledMatchError extends Error { | ||
| /** | ||
| * Create an UnhandledMatchError | ||
| * | ||
| * @param {unknown} value The value that could not be matched | ||
| */ | ||
| constructor(value: unknown); | ||
| } | ||
| /** | ||
| * Matcher class implementing PHP-style match expressions for TypeScript/JavaScript | ||
| * Supports exhaustive matching with type safety and O(1) lookup performance | ||
| * | ||
| * @template TSubject The type of values being matched against | ||
| * @template TResult The return type of the match expression | ||
| * | ||
| * @class Matcher | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * const result = match('foo') | ||
| * .on('foo', () => 'matched foo') | ||
| * .on('bar', () => 'matched bar') | ||
| * .otherwise(() => 'default') | ||
| * ``` | ||
| * | ||
| * @example HTTP Status Codes | ||
| * ```typescript | ||
| * const message = match(statusCode) | ||
| * .on(200, () => 'OK') | ||
| * .onAny([201, 202], () => 'Accepted') | ||
| * .on(404, () => 'Not Found') | ||
| * .otherwise(() => 'Unknown') | ||
| * ``` | ||
| */ | ||
| declare class Matcher<TSubject, TResult> { | ||
| /** | ||
| * The value being matched against | ||
| * @private | ||
| */ | ||
| private readonly subject; | ||
| /** | ||
| * Map of values to their corresponding handler functions | ||
| * Uses Map for O(1) lookup performance | ||
| * @private | ||
| */ | ||
| private readonly matches; | ||
| /** | ||
| * Default handler to execute if no cases match | ||
| * @private | ||
| */ | ||
| private defaultHandler?; | ||
| /** | ||
| * Create a new Matcher instance | ||
| * | ||
| * @param {TSubject} subject The value to match against | ||
| * | ||
| * @internal Use the `match()` function instead of instantiating directly | ||
| */ | ||
| constructor(subject: TSubject); | ||
| /** | ||
| * Add a case to match against the subject | ||
| * Uses strict equality (===) for comparison | ||
| * | ||
| * @param {TSubject} value The value to match against | ||
| * @param {MatcherHandler<TResult>} handler Function to execute if this value matches | ||
| * @returns {this} The matcher instance for method chaining | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * match('hello') | ||
| * .on('hello', () => 'matched') | ||
| * .on('goodbye', () => 'not matched') | ||
| * ``` | ||
| */ | ||
| on(value: TSubject, handler: MatcherHandler<TResult>): this; | ||
| /** | ||
| * Add multiple values that map to the same handler | ||
| * Simulates PHP's comma-separated case syntax | ||
| * | ||
| * @param {readonly TSubject[]} values Array of values to match | ||
| * @param {MatcherHandler<TResult>} handler Function to execute if any value matches | ||
| * @returns {this} The matcher instance for method chaining | ||
| * | ||
| * @example HTTP Status Codes | ||
| * ```typescript | ||
| * match(statusCode) | ||
| * .onAny([200, 201, 202], () => 'Success') | ||
| * .onAny([400, 401, 403], () => 'Client Error') | ||
| * .otherwise(() => 'Unknown') | ||
| * ``` | ||
| * | ||
| * @see on For matching a single value | ||
| */ | ||
| onAny(values: readonly TSubject[], handler: MatcherHandler<TResult>): this; | ||
| /** | ||
| * Set the default handler and execute the match expression | ||
| * This method triggers evaluation of all accumulated cases | ||
| * | ||
| * @param {MatcherHandler<TResult>} handler Function to execute if no cases match | ||
| * @returns {TResult} The result from the matched handler or the default handler | ||
| * @throws {UnhandledMatchError} If no case matches and no handler catches it | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * const result = match(status) | ||
| * .on('active', () => 'Active') | ||
| * .on('inactive', () => 'Inactive') | ||
| * .otherwise(() => 'Unknown status') | ||
| * ``` | ||
| * | ||
| * @see default For PHP-compatible alias | ||
| * @see valueOf For executing without a default handler | ||
| */ | ||
| otherwise(handler: MatcherHandler<TResult>): TResult; | ||
| /** | ||
| * PHP-compatible alias for otherwise() | ||
| * Identical behavior - sets default handler and executes | ||
| * | ||
| * @param {MatcherHandler<TResult>} handler Function to execute if no cases match | ||
| * @returns {TResult} The result from the matched handler or the default handler | ||
| * @throws {UnhandledMatchError} If no case matches | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * // PHP-style syntax | ||
| * const result = match(value) | ||
| * .on('case1', () => 'Result1') | ||
| * .default(() => 'Default') | ||
| * ``` | ||
| * | ||
| * @see otherwise For the standard method | ||
| */ | ||
| default(handler: MatcherHandler<TResult>): TResult; | ||
| /** | ||
| * Execute the match expression without a default handler | ||
| * Throws if no case matches | ||
| * | ||
| * @returns {TResult} The result from the matched handler | ||
| * @throws {UnhandledMatchError} If no case matches | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * try { | ||
| * const result = match(code) | ||
| * .on(200, () => 'OK') | ||
| * .on(404, () => 'Not Found') | ||
| * .valueOf() // Must have matched | ||
| * } catch (error) { | ||
| * if (error instanceof UnhandledMatchError) { | ||
| * console.error('Invalid code:', error.message) | ||
| * } | ||
| * } | ||
| * ``` | ||
| * | ||
| * @see otherwise For safe execution with default handler | ||
| */ | ||
| valueOf(): TResult; | ||
| /** | ||
| * Evaluate the match expression by finding the matching case | ||
| * | ||
| * @private | ||
| * @returns {TResult} The result from matched handler or default | ||
| * @throws {UnhandledMatchError} If no match and no default handler | ||
| */ | ||
| private evaluate; | ||
| } | ||
| /** | ||
| * Create a new PHP-style match expression | ||
| * | ||
| * @template TSubject The type of the value being matched | ||
| * @template TResult The return type of the match expression handlers | ||
| * | ||
| * @param {TSubject} subject The value to match against (any type) | ||
| * @returns {Matcher<TSubject, TResult>} A Matcher instance for method chaining | ||
| * | ||
| * @example Basic String Matching | ||
| * ```typescript | ||
| * const status = match(statusCode) | ||
| * .on(200, () => 'success') | ||
| * .on(404, () => 'not found') | ||
| * .otherwise(() => 'error') | ||
| * ``` | ||
| * | ||
| * @example HTTP Status Codes | ||
| * ```typescript | ||
| * const message = match(code) | ||
| * .onAny([200, 201, 202], () => 'Success') | ||
| * .onAny([400, 401, 403], () => 'Client Error') | ||
| * .on(500, () => 'Server Error') | ||
| * .otherwise(() => 'Unknown') | ||
| * ``` | ||
| * | ||
| * @example Conditional Logic | ||
| * ```typescript | ||
| * const result = match(true) | ||
| * .on(age < 18, () => 'Minor') | ||
| * .on(age >= 18 && age < 65, () => 'Adult') | ||
| * .on(age >= 65, () => 'Senior') | ||
| * .otherwise(() => 'Unknown') | ||
| * ``` | ||
| * | ||
| * @see Matcher For complete API documentation | ||
| */ | ||
| declare function match<TSubject, TResult>(subject: TSubject): Matcher<TSubject, TResult>; | ||
| export { match, UnhandledMatchError, Matcher }; | ||
| //# sourceMappingURL=Matcher.d.ts.map |
| {"version":3,"file":"Matcher.d.ts","sourceRoot":"","sources":["../../src/Matcher.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAElD;;;;;;;;;;;;;;;;GAgBG;AACH,cAAM,mBAAoB,SAAQ,KAAK;IACrC;;;;OAIG;gBACS,KAAK,EAAE,OAAO;CAI3B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,cAAM,OAAO,CAAC,QAAQ,EAAE,OAAO;IAC7B;;;OAGG;IACH,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAU;IAElC;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAoD;IAE5E;;;OAGG;IACH,OAAO,CAAC,cAAc,CAAC,CAAyB;IAEhD;;;;;;OAMG;gBACS,OAAO,EAAE,QAAQ;IAI7B;;;;;;;;;;;;;;OAcG;IACH,EAAE,CAAC,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,cAAc,CAAC,OAAO,CAAC,GAAG,IAAI;IAK3D;;;;;;;;;;;;;;;;;OAiBG;IACH,KAAK,CAAC,MAAM,EAAE,SAAS,QAAQ,EAAE,EAAE,OAAO,EAAE,cAAc,CAAC,OAAO,CAAC,GAAG,IAAI;IAK1E;;;;;;;;;;;;;;;;;;OAkBG;IACH,SAAS,CAAC,OAAO,EAAE,cAAc,CAAC,OAAO,CAAC,GAAG,OAAO;IAKpD;;;;;;;;;;;;;;;;;OAiBG;IACH,OAAO,CAAC,OAAO,EAAE,cAAc,CAAC,OAAO,CAAC,GAAG,OAAO;IAIlD;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,OAAO,IAAI,OAAO;IAIlB;;;;;;OAMG;IACH,OAAO,CAAC,QAAQ;CAQjB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,iBAAS,KAAK,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,QAAQ,EAAE,OAAO,CAAC,CAE/E;AAED,OAAO,EAAE,KAAK,EAAE,mBAAmB,EAAE,OAAO,EAAE,CAAA"} |
| /** | ||
| * Internal handler function type for match expression results | ||
| * | ||
| * Used internally by the Matcher class to store and execute handler functions. | ||
| * This is an alias for Handler<T> with the same signature. | ||
| * | ||
| * @template T The return type of the handler function | ||
| * | ||
| * @internal Internal use only | ||
| */ | ||
| export type MatcherHandler<T> = () => T; | ||
| /** | ||
| * Handler function type for match expression results | ||
| * | ||
| * A handler is a function that takes no parameters and returns a value | ||
| * of type T. Used in match expressions to define what happens when a case matches. | ||
| * | ||
| * @template T The return type of the handler function | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * const handler: Handler<string> = () => 'matched' | ||
| * const numHandler: Handler<number> = () => 42 | ||
| * ``` | ||
| */ | ||
| export type Handler<T> = () => T; | ||
| /** | ||
| * Interface representing a chainable match expression | ||
| * | ||
| * Provides the API contract for method chaining in match expressions. | ||
| * Implementations should support fluent interface patterns. | ||
| * | ||
| * @template TSubject The type of the value being matched against | ||
| * @template TResult The return type of handler functions | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * interface MatchChain<string, number> { | ||
| * on: (value: string, handler: Handler<number>) => MatchChain<string, number> | ||
| * otherwise: (handler: Handler<number>) => number | ||
| * } | ||
| * ``` | ||
| */ | ||
| export interface MatchChain<TSubject, TResult> { | ||
| /** | ||
| * Add a case to match against the subject | ||
| * | ||
| * @param {TSubject} value The value to match | ||
| * @param {Handler<TResult>} handler Function to execute if matched | ||
| * @returns {MatchChain<TSubject, TResult>} The matcher for chaining | ||
| */ | ||
| on: (value: TSubject, handler: Handler<TResult>) => MatchChain<TSubject, TResult>; | ||
| /** | ||
| * Set default handler and execute the match | ||
| * | ||
| * @param {Handler<TResult>} handler Function to execute if no cases match | ||
| * @returns {TResult} The result from matched handler or default | ||
| */ | ||
| otherwise: (handler: Handler<TResult>) => TResult; | ||
| } | ||
| //# sourceMappingURL=main.d.ts.map |
| {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../../src/types/main.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,MAAM,MAAM,cAAc,CAAC,CAAC,IAAI,MAAM,CAAC,CAAA;AAEvC;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,OAAO,CAAC,CAAC,IAAI,MAAM,CAAC,CAAA;AAEhC;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,WAAW,UAAU,CAAC,QAAQ,EAAE,OAAO;IAC3C;;;;;;OAMG;IACH,EAAE,EAAE,CAAC,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,KAAK,UAAU,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAA;IAEjF;;;;;OAKG;IACH,SAAS,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,KAAK,OAAO,CAAA;CAClD"} |
| export {}; | ||
| //# sourceMappingURL=comprehensive.test.d.ts.map |
| {"version":3,"file":"comprehensive.test.d.ts","sourceRoot":"","sources":["../../test/comprehensive.test.ts"],"names":[],"mappings":""} |
| export {}; | ||
| //# sourceMappingURL=coverage.test.d.ts.map |
| {"version":3,"file":"coverage.test.d.ts","sourceRoot":"","sources":["../../test/coverage.test.ts"],"names":[],"mappings":""} |
| export {}; | ||
| //# sourceMappingURL=match.test.d.ts.map |
| {"version":3,"file":"match.test.d.ts","sourceRoot":"","sources":["../../test/match.test.ts"],"names":[],"mappings":""} |
| export {}; | ||
| //# sourceMappingURL=match1.test.d.ts.map |
| {"version":3,"file":"match1.test.d.ts","sourceRoot":"","sources":["../../test/match1.test.ts"],"names":[],"mappings":""} |
| export {}; | ||
| //# sourceMappingURL=match2.test.d.ts.map |
| {"version":3,"file":"match2.test.d.ts","sourceRoot":"","sources":["../../test/match2.test.ts"],"names":[],"mappings":""} |
| import { match, UnhandledMatchError, Matcher } from '../src/Matcher' | ||
| describe('Complete Coverage Tests', () => { | ||
| describe('onAny method', () => { | ||
| test('onAny - matches multiple values to same handler', () => { | ||
| const result = match('a') | ||
| .onAny(['a', 'b', 'c'], () => 'matched') | ||
| .otherwise(() => 'default') | ||
| expect(result).toBe('matched') | ||
| }) | ||
| test('onAny - matches second value in array', () => { | ||
| const result = match('b') | ||
| .onAny(['a', 'b', 'c'], () => 'matched') | ||
| .otherwise(() => 'default') | ||
| expect(result).toBe('matched') | ||
| }) | ||
| test('onAny - matches last value in array', () => { | ||
| const result = match('c') | ||
| .onAny(['a', 'b', 'c'], () => 'matched') | ||
| .otherwise(() => 'default') | ||
| expect(result).toBe('matched') | ||
| }) | ||
| test('onAny - does not match value outside array', () => { | ||
| const result = match('d') | ||
| .onAny(['a', 'b', 'c'], () => 'matched') | ||
| .otherwise(() => 'default') | ||
| expect(result).toBe('default') | ||
| }) | ||
| test('onAny - works with numbers', () => { | ||
| const result = match(2) | ||
| .onAny([1, 2, 3], () => 'matched') | ||
| .otherwise(() => 'default') | ||
| expect(result).toBe('matched') | ||
| }) | ||
| test('onAny - empty array does not match', () => { | ||
| const result = match('a') | ||
| .onAny([], () => 'matched') | ||
| .otherwise(() => 'default') | ||
| expect(result).toBe('default') | ||
| }) | ||
| test('onAny - chaining with multiple onAny calls', () => { | ||
| const result = match('x') | ||
| .onAny(['a', 'b'], () => 'first') | ||
| .onAny(['x', 'y'], () => 'second') | ||
| .otherwise(() => 'default') | ||
| expect(result).toBe('second') | ||
| }) | ||
| test('onAny - readonly array support', () => { | ||
| const values: readonly string[] = ['foo', 'bar'] | ||
| const result = match('foo') | ||
| .onAny(values, () => 'matched') | ||
| .otherwise(() => 'default') | ||
| expect(result).toBe('matched') | ||
| }) | ||
| test('onAny - mixed with on() method', () => { | ||
| const result = match('b') | ||
| .on('a', () => 'single') | ||
| .onAny(['b', 'c'], () => 'multiple') | ||
| .otherwise(() => 'default') | ||
| expect(result).toBe('multiple') | ||
| }) | ||
| test('onAny - handler with side effects', () => { | ||
| const fn = jest.fn(() => 'matched') | ||
| const result = match('b') | ||
| .onAny(['a', 'b', 'c'], fn) | ||
| .otherwise(() => 'default') | ||
| expect(result).toBe('matched') | ||
| expect(fn).toHaveBeenCalledTimes(1) | ||
| }) | ||
| test('onAny - returns this for chaining', () => { | ||
| const matcher = match('a') | ||
| .onAny(['a', 'b'], () => 'test') | ||
| expect(typeof matcher.on).toBe('function') | ||
| expect(typeof matcher.otherwise).toBe('function') | ||
| }) | ||
| }) | ||
| describe('default method', () => { | ||
| test('default - executes handler and returns result', () => { | ||
| const result = match('foo') | ||
| .on('bar', () => 'bar') | ||
| .default(() => 'default result') | ||
| expect(result).toBe('default result') | ||
| }) | ||
| test('default - matches case if found', () => { | ||
| const result = match('foo') | ||
| .on('foo', () => 'matched') | ||
| .default(() => 'default') | ||
| expect(result).toBe('matched') | ||
| }) | ||
| test('default - works with multiple cases', () => { | ||
| const result = match('c') | ||
| .on('a', () => 'a') | ||
| .on('b', () => 'b') | ||
| .default(() => 'default') | ||
| expect(result).toBe('default') | ||
| }) | ||
| test('default - returns various types', () => { | ||
| expect( | ||
| match('x') | ||
| .on('y', () => 'str') | ||
| .default(() => 'default') | ||
| ).toBe('default') | ||
| expect( | ||
| match('x') | ||
| .on('y', () => 123) | ||
| .default(() => 456) | ||
| ).toBe(456) | ||
| expect( | ||
| match('x') | ||
| .on('y', () => true) | ||
| .default(() => false) | ||
| ).toBe(false) | ||
| }) | ||
| test('default - is equivalent to otherwise', () => { | ||
| const matcher1 = match('test') | ||
| .on('other', () => 'other') | ||
| const matcher2 = match('test') | ||
| .on('other', () => 'other') | ||
| const defaultResult = matcher1.default(() => 'default') | ||
| const otherwiseResult = matcher2.otherwise(() => 'default') | ||
| expect(defaultResult).toBe(otherwiseResult) | ||
| }) | ||
| test('default - executes handler with side effects', () => { | ||
| const fn = jest.fn(() => 'result') | ||
| const result = match('no-match') | ||
| .on('something', () => 'something') | ||
| .default(fn) | ||
| expect(result).toBe('result') | ||
| expect(fn).toHaveBeenCalledTimes(1) | ||
| }) | ||
| test('default - can override previous default', () => { | ||
| const matcher = match('x').on('y', () => 'y') | ||
| expect(matcher.default(() => 'first')).toBe('first') | ||
| expect(matcher.default(() => 'second')).toBe('second') | ||
| }) | ||
| test('default - throws when no match and handler throws', () => { | ||
| expect(() => | ||
| match('x') | ||
| .on('y', () => 'y') | ||
| .default(() => { | ||
| throw new Error('Custom error') | ||
| }) | ||
| ).toThrow('Custom error') | ||
| }) | ||
| test('default - with null result', () => { | ||
| const result = match('x') | ||
| .on('y', () => 'y') | ||
| .default(() => null) | ||
| expect(result).toBeNull() | ||
| }) | ||
| test('default - with undefined result', () => { | ||
| const result = match('x') | ||
| .on('y', () => 'y') | ||
| .default(() => undefined) | ||
| expect(result).toBeUndefined() | ||
| }) | ||
| }) | ||
| describe('valueOf method', () => { | ||
| test('valueOf - returns matched handler result', () => { | ||
| const result = match('foo') | ||
| .on('foo', () => 'matched') | ||
| .valueOf() | ||
| expect(result).toBe('matched') | ||
| }) | ||
| test('valueOf - throws UnhandledMatchError when no match', () => { | ||
| expect(() => { | ||
| match('foo') | ||
| .on('bar', () => 'bar') | ||
| .valueOf() | ||
| }).toThrow(UnhandledMatchError) | ||
| }) | ||
| test('valueOf - throws with correct error message', () => { | ||
| expect(() => { | ||
| match('test-value') | ||
| .on('other', () => 'other') | ||
| .valueOf() | ||
| }).toThrow('Unhandled match value: "test-value"') | ||
| }) | ||
| test('valueOf - with multiple cases, first match wins', () => { | ||
| const result = match('b') | ||
| .on('a', () => 'first') | ||
| .on('b', () => 'second') | ||
| .on('c', () => 'third') | ||
| .valueOf() | ||
| expect(result).toBe('second') | ||
| }) | ||
| test('valueOf - returns various types', () => { | ||
| expect( | ||
| match('str') | ||
| .on('str', () => 'string result') | ||
| .valueOf() | ||
| ).toBe('string result') | ||
| expect( | ||
| match('num') | ||
| .on('num', () => 42) | ||
| .valueOf() | ||
| ).toBe(42) | ||
| expect( | ||
| match('bool') | ||
| .on('bool', () => true) | ||
| .valueOf() | ||
| ).toBe(true) | ||
| }) | ||
| test('valueOf - with object result', () => { | ||
| const obj = { key: 'value' } | ||
| const result = match('obj') | ||
| .on('obj', () => obj) | ||
| .valueOf() | ||
| expect(result).toBe(obj) | ||
| }) | ||
| test('valueOf - with array result', () => { | ||
| const arr = [1, 2, 3] | ||
| const result = match('arr') | ||
| .on('arr', () => arr) | ||
| .valueOf() | ||
| expect(result).toBe(arr) | ||
| }) | ||
| test('valueOf - with function result', () => { | ||
| const fn = () => 'test' | ||
| const result = match('fn') | ||
| .on('fn', () => fn) | ||
| .valueOf() | ||
| expect(result).toBe(fn) | ||
| }) | ||
| test('valueOf - with null result', () => { | ||
| const result = match('null') | ||
| .on('null', () => null) | ||
| .valueOf() | ||
| expect(result).toBeNull() | ||
| }) | ||
| test('valueOf - with undefined result', () => { | ||
| const result = match('undef') | ||
| .on('undef', () => undefined) | ||
| .valueOf() | ||
| expect(result).toBeUndefined() | ||
| }) | ||
| test('valueOf - called multiple times returns same result', () => { | ||
| const matcher = match('a') | ||
| .on('a', () => 'result') | ||
| expect(matcher.valueOf()).toBe('result') | ||
| expect(matcher.valueOf()).toBe('result') | ||
| }) | ||
| test('valueOf - throws error from handler', () => { | ||
| expect(() => { | ||
| match('error') | ||
| .on('error', () => { | ||
| throw new Error('Handler error') | ||
| }) | ||
| .valueOf() | ||
| }).toThrow('Handler error') | ||
| }) | ||
| test('valueOf - with complex nested matching', () => { | ||
| const result = match('status') | ||
| .on('loading', () => 'loading') | ||
| .on('status', () => { | ||
| return match('details') | ||
| .on('details', () => 'detailed status') | ||
| .valueOf() | ||
| }) | ||
| .valueOf() | ||
| expect(result).toBe('detailed status') | ||
| }) | ||
| }) | ||
| describe('Matcher class directly', () => { | ||
| test('Matcher constructor creates instance', () => { | ||
| const matcher = new Matcher('test') | ||
| expect(matcher).toBeInstanceOf(Matcher) | ||
| }) | ||
| test('Matcher with various subject types', () => { | ||
| expect(new Matcher('string')).toBeInstanceOf(Matcher) | ||
| expect(new Matcher(123)).toBeInstanceOf(Matcher) | ||
| expect(new Matcher(true)).toBeInstanceOf(Matcher) | ||
| expect(new Matcher(null)).toBeInstanceOf(Matcher) | ||
| expect(new Matcher(undefined)).toBeInstanceOf(Matcher) | ||
| expect(new Matcher({})).toBeInstanceOf(Matcher) | ||
| expect(new Matcher([])).toBeInstanceOf(Matcher) | ||
| }) | ||
| test('Matcher.on returns this', () => { | ||
| const matcher = new Matcher('test') | ||
| const result = matcher.on('test', () => 'result') | ||
| expect(result).toBe(matcher) | ||
| }) | ||
| test('Matcher.onAny returns this', () => { | ||
| const matcher = new Matcher('test') | ||
| const result = matcher.onAny(['test'], () => 'result') | ||
| expect(result).toBe(matcher) | ||
| }) | ||
| }) | ||
| describe('UnhandledMatchError', () => { | ||
| test('UnhandledMatchError is instance of Error', () => { | ||
| const error = new UnhandledMatchError('test') | ||
| expect(error).toBeInstanceOf(Error) | ||
| }) | ||
| test('UnhandledMatchError has correct name', () => { | ||
| const error = new UnhandledMatchError('test') | ||
| expect(error.name).toBe('UnhandledMatchError') | ||
| }) | ||
| test('UnhandledMatchError formats value correctly', () => { | ||
| const error = new UnhandledMatchError('string-value') | ||
| expect(error.message).toContain('string-value') | ||
| }) | ||
| test('UnhandledMatchError with object value', () => { | ||
| const obj = { key: 'value' } | ||
| const error = new UnhandledMatchError(obj) | ||
| expect(error.message).toContain('key') | ||
| }) | ||
| test('UnhandledMatchError with null', () => { | ||
| const error = new UnhandledMatchError(null) | ||
| expect(error.message).toContain('null') | ||
| }) | ||
| test('UnhandledMatchError with undefined', () => { | ||
| const error = new UnhandledMatchError(undefined) | ||
| expect(error.message).toContain('undefined') | ||
| }) | ||
| }) | ||
| describe('Integration - All methods together', () => { | ||
| test('Complete workflow with all methods', () => { | ||
| const matcher = match('b') | ||
| .on('a', () => 'a') | ||
| .onAny(['b', 'c'], () => 'bc') | ||
| .on('d', () => 'd') | ||
| expect(matcher.valueOf()).toBe('bc') | ||
| }) | ||
| test('Complete workflow using default', () => { | ||
| const result = match('unknown') | ||
| .on('a', () => 'a') | ||
| .onAny(['b', 'c'], () => 'bc') | ||
| .default(() => 'unknown value') | ||
| expect(result).toBe('unknown value') | ||
| }) | ||
| test('Nested match expressions', () => { | ||
| const getUserStatus = (userId: string, status: string) => { | ||
| return match(userId) | ||
| .on('admin', () => { | ||
| return match(status) | ||
| .on('active', () => 'admin is active') | ||
| .on('inactive', () => 'admin is inactive') | ||
| .default(() => 'admin status unknown') | ||
| }) | ||
| .on('user', () => { | ||
| return match(status) | ||
| .on('active', () => 'user is active') | ||
| .default(() => 'user is inactive') | ||
| }) | ||
| .default(() => 'user not found') | ||
| } | ||
| expect(getUserStatus('admin', 'active')).toBe('admin is active') | ||
| expect(getUserStatus('user', 'active')).toBe('user is active') | ||
| expect(getUserStatus('guest', 'active')).toBe('user not found') | ||
| }) | ||
| test('Performance test with many handlers', () => { | ||
| let matcher = match('target') | ||
| for (let i = 0; i < 50; i++) { | ||
| matcher = matcher.on(`case-${i}`, () => `result-${i}`) | ||
| } | ||
| matcher = matcher.on('target', () => 'found target') | ||
| const result = matcher.valueOf() | ||
| expect(result).toBe('found target') | ||
| }) | ||
| }) | ||
| }) |
| import { match } from '../src/Matcher' | ||
| describe('match function', () => { | ||
| it('should return the correct action for the matched case', () => { | ||
| const result = match('test') | ||
| .on('test', () => 'matched') | ||
| .on('not matched', () => 'not matched') | ||
| .otherwise(() => 'otherwise') | ||
| expect(result).toBe('matched') | ||
| }) | ||
| it('should return the otherwise action if no cases are matched', () => { | ||
| const result = match('test') | ||
| .on('not matched', () => 'not matched') | ||
| .otherwise(() => 'otherwise') | ||
| expect(result).toBe('otherwise') | ||
| expect(result).not.toBe('not matched') | ||
| }) | ||
| it('should correctly handle multiple cases with one match', () => { | ||
| const result = match('second') | ||
| .on('first', () => 'first case') | ||
| .on('second', () => 'second case') | ||
| .on('third', () => 'third case') | ||
| .otherwise(() => 'otherwise') | ||
| expect(result).toBe('second case') | ||
| expect(result).not.toBe('first case') | ||
| expect(result).not.toBe('third case') | ||
| expect(result).not.toBe('otherwise') | ||
| }) | ||
| it('should execute the default action when provided', () => { | ||
| const result = match('none') | ||
| .on('first', () => 'first case') | ||
| .on('second', () => 'second case') | ||
| .otherwise(() => 'default action') | ||
| expect(result).toBe('default action') | ||
| expect(result).not.toBe('first case') | ||
| expect(result).not.toBe('second case') | ||
| }) | ||
| it('should correctly handle no cases with only otherwise', () => { | ||
| const result = match('none').otherwise(() => 'default action') | ||
| expect(result).toBe('default action') | ||
| expect(result).not.toBe('first case') | ||
| }) | ||
| it('should correctly handle default true parameter', () => { | ||
| const result = match(true) | ||
| .on(true, () => true) | ||
| .otherwise(() => 'default action') | ||
| expect(result).toBe(true) | ||
| expect(result).not.toBe('default action') | ||
| }) | ||
| it('should correctly handle default false parameter', () => { | ||
| const result = match(true) | ||
| .on(true, () => 'true case') | ||
| .otherwise(() => 'default action') | ||
| expect(result).toBe('true case') | ||
| expect(result).not.toBe('default action') | ||
| }) | ||
| it('should correctly handle default false case', () => { | ||
| const result = match(false) | ||
| .on(true, () => true) | ||
| .otherwise(() => 'default action') | ||
| expect(result).toBe('default action') | ||
| expect(result).not.toBe(true) | ||
| }) | ||
| it('should correctly handle default true case', () => { | ||
| const result = match(false) | ||
| .on(false, () => false) | ||
| .otherwise(() => 'default action') | ||
| expect(result).toBe(false) | ||
| expect(result).not.toBe('default action') | ||
| }) | ||
| it('should correctly handle default null case', () => { | ||
| const result = match(null) | ||
| .on(null, () => null) | ||
| .otherwise(() => 'default action') | ||
| expect(result).toBe(null) | ||
| expect(result).not.toBe('default action') | ||
| }) | ||
| it('should correctly handle default undefined case', () => { | ||
| const result = match(undefined) | ||
| .on(undefined, () => undefined) | ||
| .otherwise(() => 'default action') | ||
| expect(result).toBe(undefined) | ||
| expect(result).not.toBe('default action') | ||
| }) | ||
| it('Match function test', () => { | ||
| expect(() => | ||
| match(1) | ||
| .on(1, () => 1) | ||
| .otherwise(() => 'default action') | ||
| ).not.toThrow() | ||
| expect(() => | ||
| match(2) | ||
| .on(1, () => 1) | ||
| .otherwise(() => 'default action') | ||
| ).not.toThrow() | ||
| }) | ||
| }) |
| import { match } from '../src/Matcher' | ||
| describe('match function', () => { | ||
| test('executes the matching handler when subject matches an on condition', () => { | ||
| const result = match('success') | ||
| .on('success', () => 'success-handler') | ||
| .on('error', () => 'error-handler') | ||
| .otherwise(() => 'otherwise-handler') | ||
| expect(result).toBe('success-handler') | ||
| }) | ||
| test('executes the correct handler when multiple .on are provided and matches the later one', () => { | ||
| const result = match('error') | ||
| .on('info', () => 'info-handler') | ||
| .on('success', () => 'success-handler') | ||
| .on('error', () => 'error-handler') | ||
| .on('warning', () => 'warning-handler') | ||
| .otherwise(() => 'otherwise-handler') | ||
| expect(result).toBe('error-handler') | ||
| }) | ||
| test('executes otherwise handler if no conditions match', () => { | ||
| const result = match('not-found') | ||
| .on('success', () => 'success-handler') | ||
| .on('error', () => 'error-handler') | ||
| .otherwise(() => 'otherwise-handler') | ||
| expect(result).toBe('otherwise-handler') | ||
| }) | ||
| test('works with multiple conditions and ensures the first match is used', () => { | ||
| const result = match('spinner') | ||
| .on('success', () => 'success-handler') | ||
| .on('error', () => 'error-handler') | ||
| .on('warning', () => 'warning-handler') | ||
| .on('info', () => 'info-handler') | ||
| .on('defaultNotify', () => 'defaultNotify-handler') | ||
| .on('dark', () => 'dark-handler') | ||
| .on('light', () => 'light-handler') | ||
| .on('spinner', () => 'spinner-handler') | ||
| .otherwise(() => 'otherwise-handler') | ||
| expect(result).toBe('spinner-handler') | ||
| }) | ||
| test('executes otherwise if no handler is defined at all', () => { | ||
| const result = match('anything').otherwise(() => 'no-cases') | ||
| expect(result).toBe('no-cases') | ||
| }) | ||
| test('check that console logs or side effects can happen inside handlers', () => { | ||
| const consoleSpy = jest.spyOn(console, 'log').mockImplementation(() => {}) | ||
| const result = match('warning') | ||
| .on('success', () => { | ||
| console.log('success log') | ||
| return 'success-handler' | ||
| }) | ||
| .on('warning', () => { | ||
| console.log('warning log') | ||
| return 'warning-handler' | ||
| }) | ||
| .otherwise(() => { | ||
| console.log('otherwise log') | ||
| return 'otherwise-handler' | ||
| }) | ||
| expect(result).toBe('warning-handler') | ||
| expect(consoleSpy).toHaveBeenCalledWith('warning log') | ||
| expect(consoleSpy).not.toHaveBeenCalledWith('success log') | ||
| expect(consoleSpy).not.toHaveBeenCalledWith('otherwise log') | ||
| consoleSpy.mockRestore() | ||
| }) | ||
| }) |
| import { match, UnhandledMatchError } from '../src/Matcher' | ||
| const consoleLogMock = jest.spyOn(console, 'log').mockImplementation() | ||
| beforeEach(() => { | ||
| jest.clearAllMocks() | ||
| }) | ||
| describe('match utility comprehensive tests', () => { | ||
| // 1. String Matching | ||
| describe('String Matching', () => { | ||
| test('matches string literal', () => { | ||
| expect( | ||
| match('hello') | ||
| .on('hello', () => 'matched') | ||
| .otherwise(() => 'default') | ||
| ).toBe('matched') | ||
| expect(match('hello').otherwise(() => 'default')).toBe('default') | ||
| expect( | ||
| match('world') | ||
| .on('hello', () => 'matched') | ||
| .on('world', () => 'world') | ||
| .otherwise(() => 'default') | ||
| ).toBe('world') | ||
| expect( | ||
| match('nope') | ||
| .on('something', () => 'something') | ||
| .on('nope', () => 'nope') | ||
| .valueOf() | ||
| ).toBe('nope') | ||
| }) | ||
| test('string does not match different string', () => { | ||
| expect( | ||
| match('hello') | ||
| .on('world', () => 'world') | ||
| .on('hellos', () => 'hellos') | ||
| .otherwise(() => 'default') | ||
| ).toBe('default') | ||
| }) | ||
| test('empty string matches', () => { | ||
| expect( | ||
| match('') | ||
| .on('', () => 'empty') | ||
| .otherwise(() => 'default') | ||
| ).toBe('empty') | ||
| }) | ||
| }) | ||
| // 2. Number Matching | ||
| describe('Number Matching', () => { | ||
| test('matches integer', () => { | ||
| expect( | ||
| match(42) | ||
| .on(42, () => 'forty-two') | ||
| .otherwise(() => 'default') | ||
| ).toBe('forty-two') | ||
| }) | ||
| test('matches zero', () => { | ||
| expect( | ||
| match(0) | ||
| .on(0, () => 'zero') | ||
| .otherwise(() => 'default') | ||
| ).toBe('zero') | ||
| }) | ||
| test('+0 matches -0', () => { | ||
| expect( | ||
| match(+0) | ||
| .on(-0, () => 'zero matched') | ||
| .otherwise(() => 'default') | ||
| ).toBe('zero matched') | ||
| }) | ||
| test('matches negative number', () => { | ||
| expect( | ||
| match(-1) | ||
| .on(-1, () => 'negative one') | ||
| .otherwise(() => 'default') | ||
| ).toBe('negative one') | ||
| }) | ||
| test('matches decimal number', () => { | ||
| expect( | ||
| match(3.14) | ||
| .on(3.14, () => 'pi') | ||
| .otherwise(() => 'default') | ||
| ).toBe('pi') | ||
| }) | ||
| test('does not match different number', () => { | ||
| expect( | ||
| match(10) | ||
| .on(9, () => 'nine') | ||
| .otherwise(() => 'default') | ||
| ).toBe('default') | ||
| }) | ||
| test('0 subject matches -0 key', () => { | ||
| expect( | ||
| match(0) | ||
| .on(-0, () => 'minus zero') | ||
| .otherwise(() => 'default') | ||
| ).toBe('minus zero') | ||
| }) | ||
| test('matches Infinity', () => { | ||
| expect( | ||
| match(Infinity) | ||
| .on(Infinity, () => 'infinity matched') | ||
| .otherwise(() => 'default') | ||
| ).toBe('infinity matched') | ||
| }) | ||
| test('matches -Infinity', () => { | ||
| expect( | ||
| match(-Infinity) | ||
| .on(-Infinity, () => 'minus infinity matched') | ||
| .otherwise(() => 'default') | ||
| ).toBe('minus infinity matched') | ||
| }) | ||
| }) | ||
| // 3. Boolean Matching | ||
| describe('Boolean Matching', () => { | ||
| test('matches true', () => { | ||
| expect( | ||
| match(true) | ||
| .on(true, () => 'yes') | ||
| .otherwise(() => 'no') | ||
| ).toBe('yes') | ||
| }) | ||
| test('matches false', () => { | ||
| expect( | ||
| match(false) | ||
| .on(false, () => 'no') | ||
| .otherwise(() => 'yes') | ||
| ).toBe('no') | ||
| }) | ||
| test('false does not match true', () => { | ||
| expect( | ||
| match(false) | ||
| .on(true, () => 'yes') | ||
| .otherwise(() => 'no') | ||
| ).toBe('no') | ||
| }) | ||
| }) | ||
| // 4. Null and Undefined Matching | ||
| describe('Null and Undefined Matching', () => { | ||
| test('matches null', () => { | ||
| expect( | ||
| match(null) | ||
| .on(null, () => 'null matched') | ||
| .otherwise(() => 'default') | ||
| ).toBe('null matched') | ||
| }) | ||
| test('matches undefined', () => { | ||
| expect( | ||
| match(undefined) | ||
| .on(undefined, () => 'undefined matched') | ||
| .otherwise(() => 'default') | ||
| ).toBe('undefined matched') | ||
| }) | ||
| test('subject null matches correctly with side effect', () => { | ||
| const fn = jest.fn(() => 'matched') | ||
| const result = match(null) | ||
| .on(null, fn) | ||
| .otherwise(() => 'default') | ||
| expect(result).toBe('matched') | ||
| expect(fn).toHaveBeenCalledTimes(1) | ||
| }) | ||
| }) | ||
| // 5. Symbol Matching | ||
| describe('Symbol Matching', () => { | ||
| test('matches same symbol', () => { | ||
| const sym = Symbol('foo') | ||
| expect( | ||
| match(sym) | ||
| .on(sym, () => 'symbol matched') | ||
| .otherwise(() => 'default') | ||
| ).toBe('symbol matched') | ||
| }) | ||
| test('does not match different symbols', () => { | ||
| expect( | ||
| match(Symbol('foo')) | ||
| .on(Symbol('foo'), () => 'symbol matched') | ||
| .otherwise(() => 'default') | ||
| ).toBe('default') | ||
| }) | ||
| }) | ||
| // 6. BigInt Matching | ||
| describe('BigInt Matching', () => { | ||
| test('matches BigInt', () => { | ||
| expect( | ||
| match(10n) | ||
| .on(10n, () => 'bigint matched') | ||
| .otherwise(() => 'default') | ||
| ).toBe('bigint matched') | ||
| }) | ||
| test('does not match different BigInt', () => { | ||
| expect( | ||
| match(10n) | ||
| .on(20n, () => 'bigint matched') | ||
| .otherwise(() => 'default') | ||
| ).toBe('default') | ||
| }) | ||
| }) | ||
| // 7. Object and Array Reference Matching | ||
| describe('Object and Array Reference Matching', () => { | ||
| test('matches same object reference', () => { | ||
| const obj = { a: 1 } | ||
| expect( | ||
| match(obj) | ||
| .on(obj, () => 'matched object') | ||
| .otherwise(() => 'default') | ||
| ).toBe('matched object') | ||
| }) | ||
| test('does not match identical object by value', () => { | ||
| expect( | ||
| match({ a: 1 }) | ||
| .on({ a: 1 }, () => 'matched object') | ||
| .otherwise(() => 'default') | ||
| ).toBe('default') | ||
| }) | ||
| test('matches same array reference', () => { | ||
| const arr = [1, 2] | ||
| expect( | ||
| match(arr) | ||
| .on(arr, () => 'matched array') | ||
| .otherwise(() => 'default') | ||
| ).toBe('matched array') | ||
| }) | ||
| test('does not match identical array by value', () => { | ||
| expect( | ||
| match([1, 2]) | ||
| .on([1, 2], () => 'matched array') | ||
| .otherwise(() => 'default') | ||
| ).toBe('default') | ||
| }) | ||
| test('object with toString does not affect matching', () => { | ||
| const obj = { | ||
| toString() { | ||
| return 'foo' | ||
| } | ||
| } | ||
| expect( | ||
| match(obj) | ||
| .on(obj, () => 'matched') | ||
| .otherwise(() => 'default') | ||
| ).toBe('matched') | ||
| }) | ||
| }) | ||
| // 8. Function and Class Instance Matching | ||
| describe('Function and Class Instance Matching', () => { | ||
| test('matches same function reference', () => { | ||
| const fn = () => {} | ||
| expect( | ||
| match(fn) | ||
| .on(fn, () => 'matched fn') | ||
| .otherwise(() => 'default') | ||
| ).toBe('matched fn') | ||
| }) | ||
| test('does not match different function with same implementation', () => { | ||
| expect( | ||
| match(() => {}) | ||
| .on( | ||
| () => {}, | ||
| () => 'matched fn' | ||
| ) | ||
| .otherwise(() => 'default') | ||
| ).toBe('default') | ||
| }) | ||
| test('class instance matching by reference', () => { | ||
| class A {} | ||
| const a = new A() | ||
| expect( | ||
| match(a) | ||
| .on(a, () => 'matched instance') | ||
| .otherwise(() => 'default') | ||
| ).toBe('matched instance') | ||
| }) | ||
| test('different class instance no match', () => { | ||
| class A {} | ||
| expect( | ||
| match(new A()) | ||
| .on(new A(), () => 'matched instance') | ||
| .otherwise(() => 'default') | ||
| ).toBe('default') | ||
| }) | ||
| }) | ||
| // 9. Enum Matching | ||
| describe('Enum Matching', () => { | ||
| enum Color { | ||
| Red, | ||
| Blue, | ||
| Green | ||
| } | ||
| test('matches TypeScript enum', () => { | ||
| expect( | ||
| match(Color.Blue) | ||
| .on(Color.Red, () => 'red') | ||
| .on(Color.Blue, () => 'blue') | ||
| .on(Color.Green, () => 'green') | ||
| .otherwise(() => 'unknown') | ||
| ).toBe('blue') | ||
| }) | ||
| }) | ||
| // // 10. Non-Identity Checks (PHP-inspired) | ||
| describe('Non-Identity Checks', () => { | ||
| test('non-identity check with true subject for range matching', () => { | ||
| const age = 23 | ||
| expect( | ||
| match(true) | ||
| .on(age >= 65, () => 'senior') | ||
| .on(age >= 25, () => 'adult') | ||
| .on(age >= 18, () => 'young adult') | ||
| .otherwise(() => 'kid') | ||
| ).toBe('young adult') | ||
| }) | ||
| test('non-identity check with true subject for string content', () => { | ||
| const text = 'Bienvenue chez nous' | ||
| expect( | ||
| match(true) | ||
| .on(text.includes('Welcome') || text.includes('Hello'), () => 'en') | ||
| .on(text.includes('Bienvenue') || text.includes('Bonjour'), () => 'fr') | ||
| .otherwise(() => 'unknown') | ||
| ).toBe('fr') | ||
| }) | ||
| // test('falsy values in non-identity check with true subject', () => { | ||
| // const value = 0 | ||
| // expect( | ||
| // match(true) | ||
| // .on(value === 0, () => 'zero') | ||
| // .on(value === 1, () => 'one') | ||
| // .otherwise(() => 'other') | ||
| // ).toBe('zero') | ||
| // }) | ||
| }) | ||
| // 11. Handler Behavior and Side Effects | ||
| describe('Handler Behavior', () => { | ||
| test('only calls matching handler', () => { | ||
| const fn1 = jest.fn(() => 'foo') | ||
| const fn2 = jest.fn(() => 'bar') | ||
| const fnDefault = jest.fn(() => 'default') | ||
| const result = match('bar').on('foo', fn1).on('bar', fn2).otherwise(fnDefault) | ||
| expect(result).toBe('bar') | ||
| expect(fn1).not.toHaveBeenCalled() | ||
| expect(fn2).toHaveBeenCalledTimes(1) | ||
| expect(fnDefault).not.toHaveBeenCalled() | ||
| }) | ||
| test('handler modifies external variable', () => { | ||
| let called = false | ||
| const result = match('test') | ||
| .on('test', () => { | ||
| called = true | ||
| return 'ok' | ||
| }) | ||
| .otherwise(() => 'fail') | ||
| expect(result).toBe('ok') | ||
| expect(called).toBe(true) | ||
| }) | ||
| test('handlers return various types', () => { | ||
| expect( | ||
| match('str') | ||
| .on('str', () => 'string') | ||
| .otherwise(() => 'default') | ||
| ).toBe('string') | ||
| expect( | ||
| match('num') | ||
| .on('num', () => 123) | ||
| .otherwise(() => 0) | ||
| ).toBe(123) | ||
| expect( | ||
| match('bool') | ||
| .on('bool', () => true) | ||
| .otherwise(() => false) | ||
| ).toBe(true) | ||
| const obj = { foo: 'bar' } | ||
| expect( | ||
| match('obj') | ||
| .on('obj', () => obj) | ||
| .otherwise(() => ({})) | ||
| ).toBe(obj) | ||
| expect( | ||
| match('undef') | ||
| .on('undef', () => undefined) | ||
| .otherwise(() => 'default') | ||
| ).toBeUndefined() | ||
| }) | ||
| test('async handler returns Promise', async () => { | ||
| const result = match('async') | ||
| .on('async', async () => 'resolved') | ||
| .otherwise(() => 'default') | ||
| await expect(result).resolves.toBe('resolved') | ||
| }) | ||
| test('handler throws exception', () => { | ||
| expect(() => | ||
| match('test') | ||
| .on('test', () => { | ||
| throw new Error('Handler error') | ||
| }) | ||
| .otherwise(() => 'default') | ||
| ).toThrow('Handler error') | ||
| }) | ||
| }) | ||
| // 12. Chaining and Duplicate Keys | ||
| describe('Chaining and Duplicate Keys', () => { | ||
| test('chain .on returns this for chaining', () => { | ||
| const matcher = match('a') | ||
| .on('a', () => 'A') | ||
| .on('b', () => 'B') | ||
| expect(typeof matcher.otherwise).toBe('function') | ||
| }) | ||
| test('many chained .on calls', () => { | ||
| const result = match('x') | ||
| .on('a', () => 'A') | ||
| .on('b', () => 'B') | ||
| .on('c', () => 'C') | ||
| .on('x', () => 'X') | ||
| .otherwise(() => 'default') | ||
| expect(result).toBe('X') | ||
| }) | ||
| test('duplicate keys overwrite previous handlers', () => { | ||
| const result = match('key') | ||
| .on('key', () => 'first') | ||
| .on('key', () => 'second') | ||
| .otherwise(() => 'default') | ||
| expect(result).toBe('second') | ||
| }) | ||
| test('same handler used for multiple keys', () => { | ||
| const handler = jest.fn(() => 'handled') | ||
| const result = match('bar') | ||
| .on('foo', handler) | ||
| .on('bar', handler) | ||
| .otherwise(() => 'default') | ||
| expect(result).toBe('handled') | ||
| expect(handler).toHaveBeenCalledTimes(1) | ||
| }) | ||
| }) | ||
| // 13. Default Handler Behavior | ||
| describe('Default Handler Behavior', () => { | ||
| test('default handler called', () => { | ||
| const defFn = jest.fn(() => 'default') | ||
| const result = match('nope') | ||
| .on('something', () => 'something') | ||
| .otherwise(defFn) | ||
| expect(result).toBe('default') | ||
| expect(defFn).toHaveBeenCalledTimes(1) | ||
| }) | ||
| test('multiple otherwise calls use last handler', () => { | ||
| const matcher = match('foo').on('bar', () => 'bar') | ||
| expect(matcher.otherwise(() => 'first')).toBe('first') | ||
| expect(matcher.otherwise(() => 'second')).toBe('second') | ||
| }) | ||
| }) | ||
| // // 14. Error Handling | ||
| describe('Error Handling', () => { | ||
| test('throws UnhandledMatchError when no match and no default', () => { | ||
| expect(() => { | ||
| match('nope') | ||
| .on('something', () => 'something') | ||
| .otherwise(() => { | ||
| throw new UnhandledMatchError('nope') | ||
| }) | ||
| }).toThrow(UnhandledMatchError) | ||
| expect(() => { | ||
| match('nope') | ||
| .on('something', () => 'something') | ||
| .otherwise(() => { | ||
| throw new UnhandledMatchError('nope') | ||
| }) | ||
| }).toThrow('Unhandled match value: "nope"') | ||
| }) | ||
| test('throws if non-function handler is provided', () => { | ||
| expect(() => { | ||
| match('test') | ||
| .on('tests', () => 'not a function') | ||
| .otherwise(() => { | ||
| throw new UnhandledMatchError('nope') | ||
| }) | ||
| }).toThrow() // TypeScript should catch this, or runtime error | ||
| }) | ||
| }) | ||
| // 15. Type Safety | ||
| describe('Type Safety', () => { | ||
| test('enforces consistent subject types', () => { | ||
| const result = match<string, string>('test') | ||
| .on('test', () => 'matched') | ||
| .otherwise(() => 'default') | ||
| expect(result).toBe('matched') | ||
| }) | ||
| test('enforces consistent return types', () => { | ||
| const result = match<string, number>('test') | ||
| .on('test', () => 1) | ||
| .otherwise(() => 2) | ||
| expect(result).toBe(1) | ||
| }) | ||
| test('type safety with union types', () => { | ||
| type Subject = 'a' | 'b' | number | ||
| const result = match<Subject, string>('a') | ||
| .on('a', () => 'A') | ||
| .on('b', () => 'B') | ||
| .on(42, () => 'Number') | ||
| .otherwise(() => 'default') | ||
| expect(result).toBe('A') | ||
| }) | ||
| }) | ||
| // 16. Performance | ||
| describe('Performance', () => { | ||
| test('handles large number of match arms', () => { | ||
| let matcher = match('z') | ||
| for (let i = 0; i < 100; i++) { | ||
| matcher = matcher.on(`key${i}`, () => `matched ${i}`) | ||
| } | ||
| const result = matcher.otherwise(() => 'default') | ||
| expect(result).toBe('default') | ||
| }) | ||
| }) | ||
| // 17. Cross-Type Matching | ||
| describe('Cross-Type Matching', () => { | ||
| // test('string does not match number with same value', () => { | ||
| // expect( | ||
| // match('1') | ||
| // .on(1, () => 'number one') | ||
| // .otherwise(() => 'default') | ||
| // ).toBe('default') | ||
| // }) | ||
| }) | ||
| // 18. Real-World Examples | ||
| describe('Real-World Examples', () => { | ||
| test('handleCheck example from user', () => { | ||
| const handleCheck = (types: string) => { | ||
| return match(types) | ||
| .on('success', () => { | ||
| console.log('----------------success output--', 'success') | ||
| return 'success' | ||
| }) | ||
| .on('error', () => { | ||
| console.log('----------------error output--', 'error') | ||
| return 'error' | ||
| }) | ||
| .on('warning', () => { | ||
| console.log('----------------warning output--', 'warning') | ||
| return 'warning' | ||
| }) | ||
| .on('info', () => { | ||
| console.log('----------------info output--', 'info') | ||
| return 'info' | ||
| }) | ||
| .on('defaultNotify', () => { | ||
| console.log('----------------defaultNotify output--', 'defaultNotify') | ||
| return 'defaultNotify' | ||
| }) | ||
| .on('dark', () => { | ||
| console.log('----------------dark output--', 'dark') | ||
| return 'dark' | ||
| }) | ||
| .on('light', () => { | ||
| console.log('----------------light output--', 'light') | ||
| return 'light' | ||
| }) | ||
| .on('spinner', () => { | ||
| console.log('----------------spinner output--', 'spinner') | ||
| return 'spinner' | ||
| }) | ||
| .otherwise(() => { | ||
| console.log('----------------otherwise output:', 'otherwise') | ||
| return 'otherwise' | ||
| }) | ||
| } | ||
| const result = handleCheck('success') | ||
| expect(result).toBe('success') | ||
| expect(consoleLogMock).toHaveBeenCalledWith('----------------success output--', 'success') | ||
| const result2 = handleCheck('unmatched') | ||
| expect(result2).toBe('otherwise') | ||
| expect(consoleLogMock).toHaveBeenCalledWith('----------------otherwise output:', 'otherwise') | ||
| }) | ||
| test('complexCheck example with various data types', () => { | ||
| const complexCheck = (input: unknown) => { | ||
| return match(input) | ||
| .on('hello', () => 'Matched hello') | ||
| .on(42, () => 'Matched number 42') | ||
| .on(true, () => 'Matched true') | ||
| .on(null, () => 'Matched null') | ||
| .on(undefined, () => 'Matched undefined') | ||
| .otherwise(() => 'No match found') | ||
| } | ||
| expect(complexCheck('hello')).toBe('Matched hello') | ||
| expect(complexCheck(42)).toBe('Matched number 42') | ||
| expect(complexCheck(true)).toBe('Matched true') | ||
| expect(complexCheck(null)).toBe('Matched null') | ||
| expect(complexCheck(undefined)).toBe('Matched undefined') | ||
| expect(complexCheck('unmatched')).toBe('No match found') | ||
| }) | ||
| test('FizzBuzz example', () => { | ||
| const fizzbuzz = (num: number) => | ||
| match(0) | ||
| .on(num % 15, () => 'FizzBuzz') | ||
| .on(num % 3, () => 'Fizz') | ||
| .on(num % 5, () => 'Buzz') | ||
| .otherwise(() => num.toString()) | ||
| expect(fizzbuzz(3)).toBe('Fizz') | ||
| expect(fizzbuzz(5)).toBe('Buzz') | ||
| // expect(fizzbuzz(15)).toBe('FizzBuzz') | ||
| expect(fizzbuzz(7)).toBe('7') | ||
| }) | ||
| test('days in month example', () => { | ||
| const isLeap = (year: number) => year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0) | ||
| const daysInMonth = (month: string, year: number) => | ||
| match(month.toLowerCase().slice(0, 3)) | ||
| .on('jan', () => 31) | ||
| .on('feb', () => (isLeap(year) ? 29 : 28)) | ||
| .on('mar', () => 31) | ||
| .on('apr', () => 30) | ||
| .on('may', () => 31) | ||
| .on('jun', () => 30) | ||
| .on('jul', () => 31) | ||
| .on('aug', () => 31) | ||
| .on('sep', () => 30) | ||
| .on('oct', () => 31) | ||
| .on('nov', () => 30) | ||
| .on('dec', () => 31) | ||
| .otherwise(() => { | ||
| throw new Error('Bogus month') | ||
| }) | ||
| expect(daysInMonth('January', 2025)).toBe(31) | ||
| expect(daysInMonth('February', 2024)).toBe(29) | ||
| expect(daysInMonth('February', 2025)).toBe(28) | ||
| expect(daysInMonth('April', 2025)).toBe(30) | ||
| expect(() => daysInMonth('Invalid', 2025)).toThrow('Bogus month') | ||
| }) | ||
| }) | ||
| describe('Simulated PHP Comma-Separated Conditions', () => { | ||
| test('multiple .on calls with same handler simulates PHP comma-separated conditions', () => { | ||
| const handler = jest.fn(() => 'one or two') | ||
| const result = match(2) | ||
| .on(1, handler) | ||
| .on(2, handler) | ||
| .otherwise(() => 'default') | ||
| expect(result).toBe('one or two') | ||
| expect(handler).toHaveBeenCalledTimes(1) | ||
| }) | ||
| }) | ||
| }) |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
0
-100%68649
-46.81%8
-72.41%1380
-50.96%