Last active
November 21, 2018 04:22
-
-
Save kotarella1110/635fb150afa9dfc63bf96931e2bb2e58 to your computer and use it in GitHub Desktop.
Counter app store example - TypeScript + React + Redux + redux-saga + typescript-fsa: https://gist.github.com/kotarella1110/e31fda608ab7182fec33c5e5737105be
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { actions, asyncActions, reducer } from './counter'; | |
describe('counter actions', () => { | |
it('increment should create counter/INCREMENT action', () => { | |
expect(actions.increment()).toEqual({ | |
type: 'counter/INCREMENT', | |
}); | |
}); | |
it('decrement should create counter/DECREMENT action', () => { | |
expect(actions.decrement()).toEqual({ | |
type: 'counter/DECREMENT', | |
}); | |
}); | |
}); | |
describe('counter async actions', () => { | |
it('incrementAsync.started should create counter/INCREMENT_ASYNC_STARTED action', () => { | |
expect(asyncActions.incrementAsync.started({})).toEqual({ | |
type: 'counter/INCREMENT_ASYNC_STARTED', | |
payload: {}, | |
}); | |
}); | |
it('incrementAsync.done should create counter/INCREMENT_ASYNC_DONE action', () => { | |
expect( | |
asyncActions.incrementAsync.done({ | |
params: {}, | |
result: {}, | |
}) | |
).toEqual({ | |
type: 'counter/INCREMENT_ASYNC_DONE', | |
payload: { | |
params: {}, | |
result: {}, | |
}, | |
}); | |
}); | |
it('incrementAsync.failed should create counter/INCREMENT_ASYNC_FAILED action', () => { | |
expect( | |
asyncActions.incrementAsync.failed({ | |
params: {}, | |
error: {}, | |
}) | |
).toEqual({ | |
type: 'counter/INCREMENT_ASYNC_FAILED', | |
payload: { | |
params: {}, | |
error: {}, | |
}, | |
error: true, | |
}); | |
}); | |
it('decrementAsync.started should create counter/DECREMENT_ASYNC_STARTED action', () => { | |
expect(asyncActions.decrementAsync.started({})).toEqual({ | |
type: 'counter/DECREMENT_ASYNC_STARTED', | |
payload: {}, | |
}); | |
}); | |
it('decrementAsync.done should create counter/DECREMENT_ASYNC_DONE action', () => { | |
expect( | |
asyncActions.decrementAsync.done({ | |
params: {}, | |
result: {}, | |
}) | |
).toEqual({ | |
type: 'counter/DECREMENT_ASYNC_DONE', | |
payload: { | |
params: {}, | |
result: {}, | |
}, | |
}); | |
}); | |
it('decrementAsync.failed should create counter/DECREMENT_ASYNC_FAILED action', () => { | |
expect( | |
asyncActions.decrementAsync.failed({ | |
params: {}, | |
error: {}, | |
}) | |
).toEqual({ | |
type: 'counter/DECREMENT_ASYNC_FAILED', | |
payload: { | |
params: {}, | |
error: {}, | |
}, | |
error: true, | |
}); | |
}); | |
}); | |
describe('counter reducer', () => { | |
it('should handle counter/INCREMENT_ASYNC_STARTED', () => { | |
expect( | |
reducer( | |
{ | |
isLoading: false, | |
errorMessage: 'Request failed', | |
count: 0, | |
}, | |
asyncActions.incrementAsync.started({}) | |
) | |
).toEqual({ | |
isLoading: true, | |
errorMessage: '', | |
count: 0, | |
}); | |
}); | |
it('should handle counter/INCREMENT_ASYNC_DONE', () => { | |
expect( | |
reducer( | |
{ | |
isLoading: true, | |
errorMessage: '', | |
count: 0, | |
}, | |
asyncActions.incrementAsync.done({ | |
params: {}, | |
result: {}, | |
}) | |
) | |
).toEqual({ | |
isLoading: false, | |
errorMessage: '', | |
count: 1, | |
}); | |
}); | |
it('should handle counter/INCREMENT_ASYNC_FAILED', () => { | |
expect( | |
reducer( | |
{ | |
isLoading: true, | |
errorMessage: '', | |
count: 1, | |
}, | |
asyncActions.incrementAsync.failed({ | |
params: {}, | |
error: {}, | |
}) | |
) | |
).toEqual({ | |
isLoading: false, | |
errorMessage: 'Request failed', | |
count: 1, | |
}); | |
}); | |
it('should handle counter/DECREMENT_ASYNC_STARTED', () => { | |
expect( | |
reducer( | |
{ | |
isLoading: false, | |
errorMessage: 'Request failed', | |
count: 1, | |
}, | |
asyncActions.decrementAsync.started({}) | |
) | |
).toEqual({ | |
isLoading: true, | |
errorMessage: '', | |
count: 1, | |
}); | |
}); | |
it('should handle counter/DECREMENT_ASYNC_DONE', () => { | |
expect( | |
reducer( | |
{ | |
isLoading: true, | |
errorMessage: '', | |
count: 1, | |
}, | |
asyncActions.decrementAsync.done({ | |
params: {}, | |
result: {}, | |
}) | |
) | |
).toEqual({ | |
isLoading: false, | |
errorMessage: '', | |
count: 0, | |
}); | |
}); | |
it('should handle counter/DECREMENT_ASYNC_FAILED', () => { | |
expect( | |
reducer( | |
{ | |
isLoading: true, | |
errorMessage: '', | |
count: 0, | |
}, | |
asyncActions.decrementAsync.failed({ | |
params: {}, | |
error: {}, | |
}) | |
) | |
).toEqual({ | |
isLoading: false, | |
errorMessage: 'Request failed', | |
count: 0, | |
}); | |
}); | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import actionCreatorFactory from 'typescript-fsa'; | |
import { reducerWithInitialState } from 'typescript-fsa-reducers'; | |
import { SagaIterator, delay } from 'redux-saga'; | |
import { takeEvery, call, put, cancelled } from 'redux-saga/effects'; | |
export type Action = | |
| ReturnType<typeof increment> | |
| ReturnType<typeof decrement>; | |
const actionCreator = actionCreatorFactory('counter'); | |
const increment = actionCreator('INCREMENT'); | |
const decrement = actionCreator('DECREMENT'); | |
const incrementAsync = actionCreator.async('INCREMENT_ASYNC'); | |
const decrementAsync = actionCreator.async('DECREMENT_ASYNC'); | |
export const actions = { increment, decrement }; | |
export const asyncActions = { incrementAsync, decrementAsync }; | |
export function* incrementAsyncWorker(): SagaIterator { | |
yield put(asyncActions.incrementAsync.started({})); | |
try { | |
yield call(delay, 1000); | |
if (Math.random() > 0.8) { | |
throw new Error(); | |
} | |
yield put(asyncActions.incrementAsync.done({ params: {}, result: {} })); | |
} catch { | |
yield put(asyncActions.incrementAsync.failed({ params: {}, error: {} })); | |
} finally { | |
if (yield cancelled()) { | |
yield put(asyncActions.incrementAsync.failed({ params: {}, error: {} })); | |
} | |
} | |
} | |
export function* decrementAsyncWorker(): SagaIterator { | |
yield put(asyncActions.decrementAsync.started({})); | |
try { | |
yield call(delay, 1000); | |
if (Math.random() > 0.8) { | |
throw new Error(); | |
} | |
yield put(asyncActions.decrementAsync.done({ params: {}, result: {} })); | |
} catch { | |
yield put(asyncActions.decrementAsync.failed({ params: {}, error: {} })); | |
} finally { | |
if (yield cancelled()) { | |
yield put(asyncActions.incrementAsync.failed({ params: {}, error: {} })); | |
} | |
} | |
} | |
export function* rootSaga(): SagaIterator { | |
yield takeEvery(actions.increment.type, incrementAsyncWorker); | |
yield takeEvery(actions.decrement.type, decrementAsyncWorker); | |
} | |
export interface CounterState { | |
readonly isLoading: boolean; | |
readonly errorMessage: string; | |
readonly count: number; | |
} | |
export interface State { | |
readonly counter: CounterState; | |
} | |
const initialState: CounterState = { | |
isLoading: false, | |
errorMessage: '', | |
count: 0, | |
}; | |
export const reducer = reducerWithInitialState(initialState) | |
.cases( | |
[asyncActions.incrementAsync.started, asyncActions.decrementAsync.started], | |
state => ({ | |
...state, | |
isLoading: true, | |
errorMessage: '', | |
}) | |
) | |
.cases( | |
[asyncActions.incrementAsync.failed, asyncActions.decrementAsync.failed], | |
state => ({ | |
...state, | |
isLoading: false, | |
errorMessage: 'Request failed', | |
}) | |
) | |
.case(asyncActions.incrementAsync.done, state => ({ | |
...state, | |
isLoading: false, | |
count: state.count + 1, | |
})) | |
.case(asyncActions.decrementAsync.done, state => ({ | |
...state, | |
isLoading: false, | |
count: state.count - 1, | |
})); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment