redux-sessionstorage-simple
Version: 
Save and load Redux state to and from SessionStorage.
489 lines (390 loc) • 14 kB
JavaScript
import { createStore, applyMiddleware, combineReducers } from 'redux'
import { save, load, combineLoads, clear } from '../src/index' // redux-sessionstorage-simple dist
import equal from 'deep-equal'
const NAMESPACE_DEFAULT = 'redux_sessionstorage_simple'
const NAMESPACE_TEST = 'namespace_test'
const NAMESPACE_SEPARATOR_TEST = '**'
// -------------------------------------------------------------------------------
// Actions
var APPEND = 'APPEND'
var ADD = 'ADD'
var MODIFY = 'MODIFY'
var NOOP = 'NOOP'
// -------------------------------------------------------------------------------
var initialStateReducerA = {
  x: 'abc'
}
var initialStateReducerB = {
  y: 0
}
var initialStateReducerMultipleLevels = {
  setting1: false,
  setting2: false,
  setting3: {
    level1: {
      level2: 'hello'
    }
  }
}
var initialStateReducers = {
  reducerA: initialStateReducerA,
  reducerB: initialStateReducerB
}
var initialStateReducersPlusMultipleLevels = {
  reducerMultipleLevels: initialStateReducerMultipleLevels
}
// -------------------------------------------------------------------------------
var reducerA = function (state = initialStateReducerA, action) {
  switch (action.type) {
    case APPEND:
      return {
        x: state.x + 'x'
      }
    case NOOP:
      return state
    default:
      return state
  }
}
var reducerB = function (state = initialStateReducerB, action) {
  switch (action.type) {
    case ADD:
      return {
        y: state.y + 1
      }
    default:
      return state
  }
}
var reducerMultipleLevels = function (state = initialStateReducerMultipleLevels, action) {
  switch (action.type) {
    case MODIFY:
      return {
        setting1: true, // modified
        setting2: false,
        setting3: {
          level1: {
            level2: 'helloEdited' // modified
          }
        }
      }
    case NOOP:
      return state
    default:
      return state
  }
}
// -------------------------------------------------------------------------------
// TEST 0 - SessionStorage, are you there?
// -------------------------------------------------------------------------------
{
  if (typeof sessionStorage === 'object') {
    outputTestResult('test0', true)
  } else {
    outputTestResult('test0', false)
  }
}
// -------------------------------------------------------------------------------
// TEST 1 - Save and load entire Redux state tree
// -------------------------------------------------------------------------------
clearTestData()
{
  let middleware = save()
  // Store which saves to SessionStorage
  let storeA = applyMiddleware(middleware)(createStore)(
    combineReducers({ reducerA, reducerB }),
    initialStateReducers
  )
  // Trigger a save to SessionStorage using a noop action
  storeA.dispatch({ type: NOOP })
  // Store which loads from SessionStorage
  let storeB = createStore(
    combineReducers({ reducerA, reducerB }),
    load()
  )
  let testResult = equal(
    storeA.getState(),
    storeB.getState()
  )
  outputTestResult('test1', testResult)
}
// -------------------------------------------------------------------------------
// TEST 2 - Save and load part of the Redux state tree
// -------------------------------------------------------------------------------
clearTestData()
{
  let middleware = save({ states: ['reducerA'] })
  // Store which saves to SessionStorage
  let storeA = applyMiddleware(middleware)(createStore)(
    combineReducers({ reducerA, reducerB }),
    initialStateReducers
  )
  // Trigger a save to SessionStorage using an append action
  storeA.dispatch({ type: APPEND })
  // Store which loads from SessionStorage
  let storeB = createStore(
    combineReducers({ reducerA, reducerB }),
    load({ states: ['reducerA'] })
  )
  let testResult = equal(
    storeA.getState(),
    storeB.getState()
  )
  outputTestResult('test2', testResult)
}
// -------------------------------------------------------------------------------
// TEST 3 - Save and load entire Redux state tree under a specified namespace
// -------------------------------------------------------------------------------
clearTestData()
{
  let middleware = save({ namespace: NAMESPACE_TEST })
  // Store which saves to SessionStorage
  let storeA = applyMiddleware(middleware)(createStore)(
    combineReducers({ reducerA, reducerB }),
    initialStateReducers
  )
  // Trigger a save to SessionStorage using a noop action
  storeA.dispatch({ type: NOOP })
  // Store which loads from SessionStorage
  let storeB = createStore(
    combineReducers({ reducerA, reducerB }),
    load({ namespace: NAMESPACE_TEST })
  )
  let testResult = equal(
    storeA.getState(),
    storeB.getState()
  )
  outputTestResult('test3', testResult)
}
// -------------------------------------------------------------------------------
// TEST 4 - Save and load part of the Redux state tree under a specified namespace
// -------------------------------------------------------------------------------
clearTestData()
{
  let middleware = save({ states: ['reducerA'], namespace: NAMESPACE_TEST })
  // Store which saves to SessionStorage
  let storeA = applyMiddleware(middleware)(createStore)(
    combineReducers({ reducerA, reducerB }),
    initialStateReducers
  )
  // Trigger a save to SessionStorage using an append action
  storeA.dispatch({ type: APPEND })
  // Store which loads from SessionStorage
  let storeB = createStore(
    combineReducers({ reducerA, reducerB }),
    load({ states: ['reducerA'], namespace: NAMESPACE_TEST })
  )
  let testResult = equal(
    storeA.getState(),
    storeB.getState()
  )
  outputTestResult('test4', testResult)
}
// -------------------------------------------------------------------------------------------------
// TEST 5 - Save and load entire Redux state tree under a specified namespace and namespaceSeparator
// -------------------------------------------------------------------------------------------------
clearTestData()
{
  let middleware = save({ namespace: NAMESPACE_TEST, namespaceSeparator: NAMESPACE_SEPARATOR_TEST })
  // Store which saves to SessionStorage
  let storeA = applyMiddleware(middleware)(createStore)(
    combineReducers({ reducerA, reducerB }),
    initialStateReducers
  )
  // Trigger a save to SessionStorage using a noop action
  storeA.dispatch({ type: NOOP })
  // Store which loads from SessionStorage
  let storeB = createStore(
    combineReducers({ reducerA, reducerB }),
    load({ namespace: NAMESPACE_TEST, namespaceSeparator: NAMESPACE_SEPARATOR_TEST })
  )
  let testResult = equal(
    storeA.getState(),
    storeB.getState()
  )
  outputTestResult('test5', testResult)
}
// ------------------------------------------------------------------------------------------------------
// TEST 6 - Save and load part of the Redux state tree under a specified namespace and namespaceSeparator
// ------------------------------------------------------------------------------------------------------
clearTestData()
{
  let middleware = save({ states: ['reducerA'], namespace: NAMESPACE_TEST, namespaceSeparator: NAMESPACE_SEPARATOR_TEST })
  // Store which saves to SessionStorage
  let storeA = applyMiddleware(middleware)(createStore)(
    combineReducers({ reducerA, reducerB }),
    initialStateReducers
  )
  // Trigger a save to SessionStorage using an append action
  storeA.dispatch({ type: APPEND })
  // Store which loads from SessionStorage
  let storeB = createStore(
    combineReducers({ reducerA, reducerB }),
    load({ states: ['reducerA'], namespace: NAMESPACE_TEST, namespaceSeparator: NAMESPACE_SEPARATOR_TEST })
  )
  let testResult = equal(
    storeA.getState(),
    storeB.getState()
  )
  outputTestResult('test6', testResult)
}
// -------------------------------------------------------------------------------
// TEST 7 - Clear Redux state tree data saved without a specific namespace
// -------------------------------------------------------------------------------
clearTestData()
{
  // Store that saves without a namespace
  let storeA = applyMiddleware(save())(createStore)(reducerA, initialStateReducerA)
  // Trigger a save to SessionStorage using a noop action
  storeA.dispatch({ type: NOOP })
  // Store that saves WITH a namespace
  let storeB = applyMiddleware(save({ namespace: NAMESPACE_TEST }))(createStore)(reducerA, initialStateReducerA)
  // Trigger a save to SessionStorage using a noop action
  storeB.dispatch({ type: NOOP })
  // Perform the SessionStorage clearing
  clear()
  outputTestResult('test7', true) // Default test result to true
  for (let key in sessionStorage) {
    // If data found with default namespace then clearing data has failed
    if (key.slice(0, NAMESPACE_DEFAULT.length) === NAMESPACE_DEFAULT) {
      // Fail the test
      outputTestResult('test7', false)
    }
  }
}
// -------------------------------------------------------------------------------
// TEST 8 - Clear Redux state tree data saved with a specific namespace
// -------------------------------------------------------------------------------
clearTestData()
{
  // Store that saves without a namespace
  let storeA = applyMiddleware(save())(createStore)(reducerA, initialStateReducerA)
  // Trigger a save to SessionStorage using a noop action
  storeA.dispatch({ type: NOOP })
  // Store that saves WITH a namespace
  let storeB = applyMiddleware(save({ namespace: NAMESPACE_TEST }))(createStore)(reducerA, initialStateReducerA)
  // Trigger a save to SessionStorage using a noop action
  storeB.dispatch({ type: NOOP })
  // Perform the SessionStorage clearing
  clear({ namespace: NAMESPACE_TEST })
  outputTestResult('test8', true) // Default test result to true
  for (let key in sessionStorage) {
    // If data found with specified namespace then clearing data has failed
    if (key.slice(0, NAMESPACE_TEST.length) === NAMESPACE_TEST) {
      // Fail the test
      outputTestResult('test8', false)
    }
  }
}
// -------------------------------------------------------------------------------
// TEST 9 - Save Redux state with debouncing
// -------------------------------------------------------------------------------
clearTestData()
{
  let debouncingPeriod = 500
  // Store that saves with a debouncing period
  let storeA = applyMiddleware(save({debounce: debouncingPeriod}))(createStore)(reducerB, initialStateReducerB)
  // Trigger a save to SessionStorage using an add action
  storeA.dispatch({ type: ADD })
  // Store which loads from SessionStorage
  let storeB = createStore(reducerB, load())
  // This test result should fail because the debouncing period has
  // delayed the data being written to SessionStorage
  let testResult = storeB.getState()['y'] === 1
  outputTestResult('test9', testResult)
  // This timeout will recheck SessionStorage after a period longer than
  // our specified debouncing period. Therefore it will see the updated
  // SessionStorage dataand the test should pass
  setTimeout(function () {
    // Store which loads from SessionStorage
    let storeC = createStore(reducerB, load())
    let testResult = storeC.getState()['y'] === 1
    outputTestResult('test9', testResult)
    // Perform the SessionStorage clearing
    clear()
  }, debouncingPeriod + 200)
}
// -------------------------------------------------------------------------------
// TEST 10 - Save and load specific properties of a <u>part</u> of Redux state tree under a specified <u>namespace</u>
// -------------------------------------------------------------------------------
clearTestData()
{
  let states = [
    'reducerMultipleLevels.setting1',
    'reducerMultipleLevels.setting3.level1.level2'
  ]
  let middleware = save({ states: states, namespace: NAMESPACE_TEST })
  // Store which saves to SessionStorage
  let storeA = applyMiddleware(middleware)(createStore)(
    combineReducers({ reducerMultipleLevels }),
    initialStateReducersPlusMultipleLevels
  )
  storeA.dispatch({ type: MODIFY })
  // Store which loads from SessionStorage
  let storeB = createStore(
    combineReducers({ reducerMultipleLevels }),
    load({
      states: states,
      namespace: NAMESPACE_TEST,
      preloadedState: initialStateReducersPlusMultipleLevels
    })
  )
  let testResult = equal(
    storeA.getState(),
    storeB.getState()
  )
  outputTestResult('test10', testResult)
}
// -------------------------------------------------------------------------------
// TEST 11 - Save and load entire Redux state tree except the states we ignore
// -------------------------------------------------------------------------------
clearTestData()
{
  let initialState = {
    z1: 0,
    z2: 'z',
    z3: 1
  }
  let successState = {
    z2: 'z'
  }
  let reducer = function (state = initialState, action) {
    switch (action.type) {
      case NOOP:
        return state
      default:
        return state
    }
  }
  let middleware = save({ ignoreStates: ['z1', 'z3'] })
  // Store which saves to SessionStorage
  let storeA = applyMiddleware(middleware)(createStore)(reducer)
  // Trigger a save to SessionStorage using a noop action
  storeA.dispatch({ type: NOOP })
  // Store which loads from SessionStorage
  let storeB = createStore(
    reducer,
    load()
  )
  let testResult = equal(
    successState,
    storeB.getState()
  )
  outputTestResult('test11', testResult)
}
// -------------------------------------------------------------------------------
// Output result of test in browser
function outputTestResult (test, testResult) {
  document.getElementById(test).innerHTML = (testResult) ? 'SUCCESS' : 'FAILED'
  document.getElementById(test).className = (testResult) ? 'true' : 'false'
}
// Clear test data in SessionStorage
function clearTestData () {
  for (let key in sessionStorage) {
    if (key.slice(0, NAMESPACE_DEFAULT.length) === NAMESPACE_DEFAULT ||
        key.slice(0, NAMESPACE_TEST.length) === NAMESPACE_TEST) {
      sessionStorage.removeItem(key)
    }
  }
}