From feb4c8cf54e506c5826ef11028d216c31e7089f1 Mon Sep 17 00:00:00 2001 From: shtse8 Date: Mon, 18 Mar 2024 06:25:47 +0800 Subject: [PATCH] Refactor Chain class and add $op function --- src/chain.ts | 49 +++++++++++++++++++---- tests/chain.test.ts | 98 ++++++++++++++++++++++++++++++++------------- 2 files changed, 111 insertions(+), 36 deletions(-) diff --git a/src/chain.ts b/src/chain.ts index 8aa6674..832f5d0 100644 --- a/src/chain.ts +++ b/src/chain.ts @@ -4,18 +4,51 @@ * @example * chain(5).pipe(add, 5).pipe(multiply, 2).unwrap() // returns 20 */ -export class Chain { - constructor(private value: T) { } +export class Chain { + constructor(private initialValue: I, private ops: ((value: any) => any)[] = []) { } - pipe(fn: (value: T, ...args: Args) => U, ...args: Args): Chain { - return new Chain(fn(this.value, ...args)); + pipe(fn: (value: O) => U): Chain { + return new Chain(this.initialValue, [...this.ops, fn]); } - unwrap(): T { - return this.value; + value(): O { + return this.ops.reduce((acc, fn) => fn(acc), this.initialValue) as unknown as O; } } +/** + * Converts a function into a format suitable for use with the `Chain.pipe` method. + * This allows for the inclusion of additional parameters to the function prior to its + * execution in the chain. `$op` effectively curries the provided function, splitting + * its application into two steps: first, `$op` is called with the function to adapt, + * returning a new function. This returned function is then called with the additional + * arguments needed for the adapted function, and it returns yet another function that + * expects the current value in the chain as its input. This final function is suitable + * for use with `Chain.pipe`, as it matches the expected signature by taking a single + * argument—the current value in the chain—and applying the original function to it, + * along with the pre-specified additional arguments. + * + * @param fn The function to be converted. This function can take multiple parameters, + * with the first parameter intended to be the current value in the chain, and the + * rest being additional arguments provided during the subsequent call. + * @returns A function that, when called with additional arguments, returns a new + * function designed for the `Chain.pipe` method. This new function takes the current + * value in the chain as its sole argument and applies the original function with the + * specified additional arguments. + * + * @example + * // Assuming `add` and `multiply` are defined as: + * // const add = (x, y) => x + y; + * // const multiply = (x, y) => x * y; + * + * // Correct usage in a chain + * chain(5).pipe($op(add)(5)).pipe($op(multiply)(2)).value(); // returns 20 + */ +export function $op(fn: (value: T, ...args: Args) => U) { + return (...args: Args) => (value: T) => fn(value, ...args); +} + + /** * Chains a value to be used in a pipeline. * @param value value to chain @@ -23,6 +56,6 @@ export class Chain { * @example * chain(5).pipe(add, 5).pipe(multiply, 2).unwrap() // returns 20 */ -export function chain(value: T): Chain { - return new Chain(value); +export function chain(value: T): Chain { + return new Chain(value); } \ No newline at end of file diff --git a/tests/chain.test.ts b/tests/chain.test.ts index 3dd0e52..3b94c5b 100644 --- a/tests/chain.test.ts +++ b/tests/chain.test.ts @@ -1,34 +1,76 @@ import { describe, test, it, expect } from 'bun:test' -import x from '../src/index' - -describe('chain', () => { - // test using basic array operations - test("Chains array operations", () => { - const arr = [1, 2, 3, 4, 5]; - const expected = [2, 4, 6, 8, 10]; - const result = x.chain(arr) - .pipe(x.map, x => x * 2) - .unwrap(); - expect(result).toEqual(expected); +import { $op, chain } from '../src/index' + +// Define a simple function for testing +const add = (x: number, y: number): number => x + y; + +describe('$op function', () => { + it('correctly curries a function with provided arguments', () => { + // Curry the `add` function with $op + const curriedAdd = $op(add); + + // Prepare the curried function with one argument (5) + const addFive = curriedAdd(5); + + // Now `addFive` should be a function that expects another number and adds 5 to it + const result = addFive(10); // This should be equivalent to `add(10, 5)` + + // Verify the result is as expected + expect(result).toBe(15); }); - // test using basic string operations - test("Chains string operations", () => { - const str = "Hello world"; - const expected = "HELLO WORLD"; - const result = x.chain(str) - .pipe(x.upperCase) - .unwrap(); - expect(result).toBe(expected); + it('returns a function that correctly applies all arguments to the original function', () => { + // Define a more complex function for testing + const subtract = (x: number, y: number, z: number): number => x - y - z; + + // Curry the `subtract` function with $op + const curriedSubtract = $op(subtract); + + // Prepare the curried function with two arguments (10, 5) + const subtractTenAndFive = curriedSubtract(10, 5); + + // Now `subtractTenAndFive` should be a function that expects another number, + // subtracts 10 and then 5 from it + const result = subtractTenAndFive(20); // This should be equivalent to `subtract(20, 10, 5)` + + // Verify the result is as expected + expect(result).toBe(5); }); +}); + +describe('Chain class', () => { + it('initializes with a value and unwraps it correctly', () => { + const initialValue = 10; + const result = chain(initialValue).value(); + + expect(result).toBe(initialValue); + }); + + it('applies a single operation to the initial value', () => { + const initialValue = 5; + const addFive = (x: number) => x + 5; + const result = chain(initialValue).pipe(addFive).value(); + + expect(result).toBe(10); + }); + + it('chains multiple operations and applies them in order', () => { + const initialValue = 5; + const addFive = (x: number) => x + 5; + const multiplyByTwo = (x: number) => x * 2; + const result = chain(initialValue).pipe(addFive).pipe(multiplyByTwo).value(); + + // Expected order of operations: (5 + 5) * 2 + expect(result).toBe(20); + }); + + it('handles operations that change the type of the value', () => { + const initialValue = 5; + const toString = (x: number) => x.toString(); + const addHello = (x: string) => `Hello ${x}`; + const result = chain(initialValue).pipe(toString).pipe(addHello).value(); - // test using basic object operations - test("Chains object operations", () => { - const obj = { a: 1, b: 2, c: 3 }; - const expected = { a: 2, b: 4, c: 6 }; - const result = x.chain(obj) - .pipe(x.mapValues, x => x * 2) - .unwrap(); - expect(result).toEqual(expected); + // Expected result is a string transformation of the initial value + expect(result).toBe("Hello 5"); }); -}) \ No newline at end of file +}); \ No newline at end of file