All files RootState.ts

100% Statements 33/33
100% Branches 12/12
100% Functions 11/11
100% Lines 33/33

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 1076x                                   6x                   24x         67x 154x 67x         52x         28x               16x 16x 16x         201x 2x   201x       24x       2x                     6x 128x 128x 128x 128x 252x 128x 60x   60x 57x     68x   124x 45x   252x   128x 32x   96x  
export { RootState };
 
 
interface StateMap {
    [key:string]: StateMap|undefined
}
 
interface Root {
    state:StateMap
}
 
declare global {
    interface Window {
        [stateProp]: StateMap;
    }
}
 
 
const stateProp = Symbol();
 
 
/**
 * Used to keep track of a global state tree
 * for data elements.
 */
class RootState {
    /** Initializes/resets the root state */
    static init(state:StateMap) {
        RootState.current = state;
    }
 
    /** Returns the state at the given state path */
    static get(path:string):object|null {
        const value = path.split(".").reduce((state:StateMap|undefined, prop:string) =>
            state !== undefined ? state[prop] : undefined, RootState.current);
        return value === undefined ? null : value;
    }
 
    /** Sets the state at the given state path */
    static set(path:string, value: object):void {    
        setState(RootState.current, path, value);
    }
 
    /** Removes the state at the given state path */
    static delete(path:string):void {
        setState(RootState.current, path, undefined);
    }
 
    /**
     * Creates a copy of the root state and sets the value at the state path.
     * Used for intermediate changes before committing.
     */
    static draft(path:string, value: object):StateMap {
        const copy = JSON.parse(JSON.stringify(RootState.current));
        const state = setState(copy, path, value);
        return state;
    }
 
    /** The current root state */
    static get current() {
        if (!window[stateProp]) {
            window[stateProp] = {};
        }
        return window[stateProp];
    }
 
    private static set current(state:StateMap) {
        window[stateProp] = state;
    }
 
    static snapshot(name:string) {
        window.dispatchEvent(new CustomEvent("rootstate-snapshot", {
            detail: {
                name,
                state: RootState.current
            }
        }));
    }
}
 
 
/** Used for setting, deleting, and drafting state */
const setState = (state:StateMap, path:string, value:object|undefined):StateMap => {
    let currentState = state;
    const pathParts = path.split(".");
    let deleteElStatePath:string|null = null;
    pathParts.forEach((prop:string, index:number) => {
        if (index === pathParts.length - 1) {
            if (value === undefined) {
                delete currentState[prop];
                // if empty, then delete that part as well
                if (Object.keys(currentState).length === 0) {
                    deleteElStatePath = [...pathParts].splice(0, pathParts.length - 1).join(".");
                }
            } else {
                currentState[prop] = { ...value } as StateMap;
            }
        } else if (currentState[prop] === undefined) {
            currentState[prop] = {};
        }
        currentState = currentState[prop] as StateMap;
    });
    if (deleteElStatePath) {
        return setState(RootState.current, deleteElStatePath, undefined);
    }
    return state;
};