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)
}
}
}