Skip to content

Instantly share code, notes, and snippets.

@ivawzh
Last active August 12, 2016 10:50
Show Gist options
  • Save ivawzh/a2074f8f735ecd6dc2b0f852a3b43eca to your computer and use it in GitHub Desktop.
Save ivawzh/a2074f8f735ecd6dc2b0f852a3b43eca to your computer and use it in GitHub Desktop.
Immutable unidirectional back-end architechure
// coordinator => saga => action => reducer => coordinator => ...
describe.only('Unidirectional back-end architechure', () => {
let store, stateHistory, dispatched
beforeEach(()=>{
const defaultState = { text: 'initial state' }
function reducer(state = defaultState, action) {
switch (action.type) {
case 'UPDATE_NUMBER':
return { ...state, number: action.number }
case 'say':
return { text: action.payload}
case 'USER_FETCH_REQUESTED':
return { text: 'USER_FETCH_REQUESTED'}
case 'USER_FETCH_SUCCEEDED':
return { text: 'USER_FETCH_SUCCEEDED', message: action.message}
case 'USER_FETCH_FAILED':
return { text: 'USER_FETCH_FAILED'}
default:
return state
}
}
function asyncThing(payload) {
return new Promise(resolve => {
setTimeout(() => {
resolve(payload)
}, 50)
})
}
function* fetchUserSaga(action) {
try {
yield put({type: 'USER_FETCH_REQUESTED'})
const message = yield call(asyncThing, action.payload);
yield put({type: "USER_FETCH_SUCCEEDED", message: message});
} catch (e) {
yield put({type: "USER_FETCH_FAILED", error: e});
}
}
function asyncPlusOne (num) {
return new Promise(resolve => {
setTimeout(()=>{ resolve(num+1)}, 50)
})
}
function* plusOneSaga(action) {
try {
const state = yield select()
const newNum = yield call(asyncPlusOne, state.number)
yield put({type: 'UPDATE_NUMBER', number: newNum })
} catch (e) {
yield put({type: 'PLUS_ONE_FAILED', error: e})
}
}
function rootSagaWatcher(sagaMiddleware) {
sagaMiddleware.run(function* () {
yield* takeEvery("SIDE_EFFECT_USER_FETCH", fetchUserSaga)
})
sagaMiddleware.run(function* () {
yield* takeEvery("SIDE_EFFECT_PLUS_ONE", plusOneSaga)
})
}
const sagaMiddleware = createSagaMiddleware()
const blockSideEffectMiddleware = store => next => action => {
if (action.type.match(/^SIDE_EFFECT/)) {
} else {
return next(action)
}
}
dispatched = []
const logMiddleware = store => next => action => {
if (action.type !== 'SIDE_EFFECT_DO_NOTHING') {
dispatched.push(JSON.stringify(action))
}
return next(action)
}
store = createStore(
reducer,
applyMiddleware(logMiddleware, sagaMiddleware, blockSideEffectMiddleware)
)
rootSagaWatcher(sagaMiddleware)
store.subscribe(() => {
const action = coordinator(store.getState())
if (action !== 'SIDE_EFFECT_DO_NOTHING') {
store.dispatch(action)
}
})
stateHistory = [store.getState()]
function coordinator(state:mixed):Action {
stateHistory.push(state)
if (state.crawlConcurrency < state.crawlConcurrencyMax) {
return { type: 'SIDE_EFFECT_CRAWL', entryPage: '123.com/items/123' }
} else if (state.persistConcurrency < state.persistConcurrencyMax) {
return { type: 'SIDE_EFFECT_PERSIST_ITEM', itemKey: key(first(state.items)) }
} else if (state.number < 4) {
return { type: 'SIDE_EFFECT_PLUS_ONE' }
} else {
return({ type: 'SIDE_EFFECT_DO_NOTHING' })
}
}
})
it('just works', () => {
store.dispatch({ type: "say", payload: 'Hello world' })
expect(stateHistory).to.eql([
{ "text": 'initial state' },
{ "text": 'Hello world' }
])
})
it('always trigger subscription upon action received by reducer', () => {
store.dispatch({ type: 'no-such-a-action-handler' })
expect(stateHistory).to.eql([
{ "text": 'initial state' },
{ "text": 'initial state' }
])
})
it('blocks saga actions', () => {
store.dispatch({ type: "SIDE_EFFECT", payload: 'Hello world' })
expect(stateHistory).to.eql([
{ "text": 'initial state' }
])
})
it('triggers saga', done => {
store.dispatch({ type: "SIDE_EFFECT_USER_FETCH", payload: 'say what' })
setTimeout(() => {
expect(stateHistory).to.eql([
{ "text": 'initial state' },
{ "text": "USER_FETCH_REQUESTED" },
{ "text": "USER_FETCH_SUCCEEDED", "message": "say what" }
])
expect(dispatched).to.eql([
"{\"type\":\"SIDE_EFFECT_USER_FETCH\",\"payload\":\"say what\"}",
"{\"type\":\"USER_FETCH_REQUESTED\"}",
"{\"type\":\"USER_FETCH_SUCCEEDED\",\"message\":\"say what\"}"
])
done()
}, 100)
})
it('triggers saga tasks parallelly', done => {
store.dispatch({ type: "SIDE_EFFECT_USER_FETCH", payload: 'say what' })
store.dispatch({ type: "SIDE_EFFECT_USER_FETCH", payload: 'say what' })
store.dispatch({ type: "SIDE_EFFECT_USER_FETCH", payload: 'say what' })
setTimeout(() => {
expect(stateHistory).to.eql([
{ "text": 'initial state' },
{ "text": "USER_FETCH_REQUESTED" },
{ "text": "USER_FETCH_REQUESTED" },
{ "text": "USER_FETCH_REQUESTED" },
{ "text": "USER_FETCH_SUCCEEDED", "message": "say what" },
{ "text": "USER_FETCH_SUCCEEDED", "message": "say what" },
{ "text": "USER_FETCH_SUCCEEDED", "message": "say what" }
])
expect(dispatched).to.eql([
"{\"type\":\"SIDE_EFFECT_USER_FETCH\",\"payload\":\"say what\"}",
"{\"type\":\"USER_FETCH_REQUESTED\"}",
"{\"type\":\"SIDE_EFFECT_USER_FETCH\",\"payload\":\"say what\"}",
"{\"type\":\"USER_FETCH_REQUESTED\"}",
"{\"type\":\"SIDE_EFFECT_USER_FETCH\",\"payload\":\"say what\"}",
"{\"type\":\"USER_FETCH_REQUESTED\"}",
"{\"type\":\"USER_FETCH_SUCCEEDED\",\"message\":\"say what\"}",
"{\"type\":\"USER_FETCH_SUCCEEDED\",\"message\":\"say what\"}",
"{\"type\":\"USER_FETCH_SUCCEEDED\",\"message\":\"say what\"}"
])
done()
}, 300)
})
it('triggers number to increment', done => {
store.dispatch({ type: 'UPDATE_NUMBER', number: 0})
setTimeout(() => {
expect(stateHistory).to.eql([
{ text: 'initial state' },
{ text: 'initial state', number: 0 },
{ text: 'initial state', number: 1 },
{ text: 'initial state', number: 2 },
{ text: 'initial state', number: 3 }
])
expect(dispatched).to.eql([
'{"type":"UPDATE_NUMBER","number":0}',
'{"type":"SIDE_EFFECT_PLUS_ONE"}',
'{"type":"UPDATE_NUMBER","number":1}',
'{"type":"SIDE_EFFECT_PLUS_ONE"}',
'{"type":"UPDATE_NUMBER","number":2}',
'{"type":"SIDE_EFFECT_PLUS_ONE"}',
'{"type":"UPDATE_NUMBER","number":3}',
'{"type":"SIDE_EFFECT_PLUS_ONE"}'
])
expect(store.getState().number).to.eq(3)
done()
}, 200)
})
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment