All files / src/Product Product.ts

100% Statements 42/42
100% Branches 2/2
100% Functions 27/27
100% Lines 26/26

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126  4x 5x                   4x         43x       4x   4x   4x   4x   4x   4x   4x                 43x 43x                       4x                 15x 15x 15x                 8x 8x                 2x                 2x                 6x 6x                 3x 3x                 34x    
import { StateController } from "../StateController";
import { produce } from "immer";
export { produce }
 
 
type NextFunction<TState> = (state:TState) => void;
type TapFunction<TState> = (product:Product<TState>) => void;
 
/**
 * A monad like class that promotes functional style
 * state changes with a StateController
 */
export class Product<TState> {
    /**
     * Creates a new Product object.
     */
    static of<TState>(controller: StateController, stateName?:string) {
        return new Product<TState>(controller, stateName || "state");
    }
 
    /** A lifting function that calls next */
    static nextWith = <TState>(product: Product<TState>) => (fn:NextFunction<TState>) => product.continue(product).next(fn);
    /** A lifting function that calls tap */
    static tapWith = <TState>(product: Product<TState>) => (fn:TapFunction<TState>) => product.continue(product).tap(fn);
    /** A chainable call to requestUpdate */
    static requestUpdate = (event:Event|string) => <TState>(product: Product<TState>) => product.requestUpdate(event);
    /** A chainable call to dispatchHostEvent */
    static dispatchHostEvent = (event:Event) => <TState>(product:Product<TState>) => product.dispatchHostEvent(event);
    /** A chainable call to next */
    static next = <TState>(fn:NextFunction<TState>) => (product: Product<TState>) => product.next(fn);
    /** A chainable call to tap */
    static tap = <TState>(fn:TapFunction<TState>) => (product: Product<TState>) => product.tap(fn);
    /** Returns the current state */
    static getState = <TState>(product: Product<TState>) => product.getState();
 
 
    /**
     * 
     * @param controller 
     * @param stateName 
     */
    constructor(controller: StateController, stateName:string) {
        this.controller = controller;
        this.stateName = stateName;
    }
 
    public controller:StateController;
    private stateName:string;
 
    /**
     * Returns a snapshot of the state property.
     * Similar to a flatten method.
     * @returns {TState}
     */
    getState():TState {
        return this.controller[this.stateName] as TState;
    }
 
    /**
     * The primary mapping function.
     * @param {NextFunction<TState>} fn
     * @returns {Product<TState>}
     */
    next(fn: NextFunction<TState>) {
        const state = this.controller[this.stateName] as TState;
        this.controller[this.stateName] = produce(state, (draft:TState) => fn(draft)) as TState;
        return this.continue(this);
    }
 
    /**
     * Use to perform branching operations.
     * @param {TapFunction<TState>} fn
     * @returns {Product<TState>}
     */
    tap(fn: TapFunction<TState>) {
        fn(this);
        return this.continue(this);
    }
 
    /**
     * Enables running multiple 'next' functions in a pipe.
     * @param  {Array<NextFunction<TState>>} fns a series of functions to call next on.
     * @returns {Product<TState>}
     */
    pipeNext(...fns: Array<NextFunction<TState>>):Product<TState> {
        return fns.reduce((v:Product<TState>, f:NextFunction<TState>) => v.next(f), this);
    }
 
    /**
     * Enables running multiple 'tap' functions in a pipe.
     * @param  {...any} fns a series of functions to call tap on.
     * @returns {Product<TState>}
     */
    pipeTap(...fns: Array<TapFunction<TState>>):Product<TState> {
        return fns.reduce((v:Product<TState>, f:TapFunction<TState>) => v.tap(f), this);
    }
 
    /**
     * Calls requestUpdate on the controller.
     * @param {Event|string} event
     * @returns {Product<TState>}
     */
    requestUpdate(event:Event|string) {
        this.controller.requestUpdate(event);
        return this.continue(this);
    }
 
    /**
     * Dispatches an event on the controllers host element.
     * @param event 
     * @returns {Product<TState>}
     */
    dispatchHostEvent(event:Event) {
        this.controller.host.dispatchEvent(event);
        return this.continue(this);
    }
 
    /**
     * Returns a new Product object based on the current one.
     * Convenient for mapping methods.
     * @returns {Product<TState>}
     */
    continue(product:Product<TState>) {
        return Product.of<TState>(product.controller, product.stateName);
    }
}