Skip to content

Instantly share code, notes, and snippets.

@kotarella1110
Last active November 21, 2018 04:22
Show Gist options
  • Save kotarella1110/635fb150afa9dfc63bf96931e2bb2e58 to your computer and use it in GitHub Desktop.
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
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,
});
});
});
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