All files undoable.js

100% Statements 20/20
85.71% Branches 6/7
100% Functions 7/7
100% Lines 18/18
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 1031x 1x 1x   1x           1x                   1x 3x 3x 3x                   1x 3x 3x 3x                   1x           17x             17x   68x                                   6x                                              
export const UNDO = 'UNDO';
export const REDO = 'REDO';
export const REPLAY_FINISHED = '@@redux-undoable/REPLAY_FINISHED';
 
const replayFinished = () => {
  return {
    type: REPLAY_FINISHED
  };
};
 
const replay = function(initial, actions, reducer) {
  return actions
    .map(action => {
      // create a copy of the action with metadata to indicate that a replay is in progress
      return { ...action, meta: { replay: true } };
    })
    .concat(replayFinished())
    .reduce((state, action) => reducer(state, action), initial);
};
 
const undo = function(initial, past, present, future, reducer) {
  const action = past[past.length - 1];
  const newPast = past.slice(0, past.length - 1);
  const previous = replay(initial, newPast, reducer);
 
  return {
    initial,
    past: newPast,
    present: previous,
    future: [ action, ...future ]
  };
};
 
const redo = function(initial, past, present, future, reducer){
  const action = future[0];
  const newFuture = future.slice(1);
  const newPresent = reducer(present, action);
 
  return {
    initial,
    past: [ ...past, action ],
    present: newPresent,
    future: newFuture
  };
};
 
const defaultConfig = {
  init: ['@@redux/INIT'],
  include: []
};
 
export default function(reducer, config) {
  const initialState = {
    initial: null,
    past: [],
    present: reducer(undefined, {}),
    future: []
  };
 
  const mergedConfig = Object.assign(defaultConfig, config);
 
  return function (state = initialState, action) {
    const { initial, past, present, future } = state;
 
    if (mergedConfig.init.includes(action.type) && !state.present) {
      return {
        initial,
        past,
        present: replay(initial, past, reducer),
        future
      };
    }
 
    switch (action.type) {
      case UNDO:
        return undo(initial, past, present, future, reducer);
      case REDO:
        return redo(initial, past, present, future, reducer);
      default:
        const newPresent = reducer(present, action);
        if (present === newPresent) {
          return state;
        }
 
        if (!mergedConfig.include.includes(action.type)) {
          return {
            initial,
            past,
            present: newPresent,
            future
          };
        }
 
        return {
          initial,
          past: [ ...past, action ],
          present: newPresent,
          future: []
        };
    }
  };
}