@evertbouw/redux-toolkit
Version:
Redux toolkit but without immer
228 lines (209 loc) • 6.12 kB
text/typescript
import { createReducer, CaseReducer } from './createReducer'
import { PayloadAction, createAction } from './createAction'
import { Reducer } from 'redux'
interface Todo {
text: string
completed?: boolean
}
interface AddTodoPayload {
newTodo: Todo
}
interface ToggleTodoPayload {
index: number
}
type TodoState = Todo[]
type TodosReducer = Reducer<TodoState, PayloadAction<any>>
type AddTodoReducer = CaseReducer<TodoState, PayloadAction<AddTodoPayload>>
type ToggleTodoReducer = CaseReducer<
TodoState,
PayloadAction<ToggleTodoPayload>
>
describe('createReducer', () => {
describe('given pure reducers with immutable updates', () => {
const addTodo: AddTodoReducer = (state, action) => {
const { newTodo } = action.payload
// Updates the state immutably
return state.concat({ ...newTodo, completed: false })
}
const toggleTodo: ToggleTodoReducer = (state, action) => {
const { index } = action.payload
// Updates the todo object immutably
return state.map((todo, i) => {
if (i !== index) return todo
return { ...todo, completed: !todo.completed }
})
}
const todosReducer = createReducer([] as TodoState, {
ADD_TODO: addTodo,
TOGGLE_TODO: toggleTodo
})
behavesLikeReducer(todosReducer)
})
describe('alternative builder callback for actionMap', () => {
const increment = createAction<number, 'increment'>('increment')
const decrement = createAction<number, 'decrement'>('decrement')
test('can be used with ActionCreators', () => {
const reducer = createReducer(0, builder =>
builder
.addCase(increment, (state, action) => state + action.payload)
.addCase(decrement, (state, action) => state - action.payload)
)
expect(reducer(0, increment(5))).toBe(5)
expect(reducer(5, decrement(5))).toBe(0)
})
test('can be used with string types', () => {
const reducer = createReducer(0, builder =>
builder
.addCase(
'increment',
(state, action: { type: 'increment'; payload: number }) =>
state + action.payload
)
.addCase(
'decrement',
(state, action: { type: 'decrement'; payload: number }) =>
state - action.payload
)
)
expect(reducer(0, increment(5))).toBe(5)
expect(reducer(5, decrement(5))).toBe(0)
})
test('can be used with ActionCreators and string types combined', () => {
const reducer = createReducer(0, builder =>
builder
.addCase(increment, (state, action) => state + action.payload)
.addCase(
'decrement',
(state, action: { type: 'decrement'; payload: number }) =>
state - action.payload
)
)
expect(reducer(0, increment(5))).toBe(5)
expect(reducer(5, decrement(5))).toBe(0)
})
test('will throw if the same type is used twice', () => {
expect(() =>
createReducer(0, builder =>
builder
.addCase(increment, (state, action) => state + action.payload)
.addCase(increment, (state, action) => state + action.payload)
.addCase(decrement, (state, action) => state - action.payload)
)
).toThrowErrorMatchingInlineSnapshot(
`"addCase cannot be called with two reducers for the same action type"`
)
expect(() =>
createReducer(0, builder =>
builder
.addCase(increment, (state, action) => state + action.payload)
.addCase('increment', state => state + 1)
.addCase(decrement, (state, action) => state - action.payload)
)
).toThrowErrorMatchingInlineSnapshot(
`"addCase cannot be called with two reducers for the same action type"`
)
})
})
})
function behavesLikeReducer(todosReducer: TodosReducer) {
it('should handle initial state', () => {
const initialAction = { type: '', payload: undefined }
expect(todosReducer(undefined, initialAction)).toEqual([])
})
it('should handle ADD_TODO', () => {
expect(
todosReducer([], {
type: 'ADD_TODO',
payload: { newTodo: { text: 'Run the tests' } }
})
).toEqual([
{
text: 'Run the tests',
completed: false
}
])
expect(
todosReducer(
[
{
text: 'Run the tests',
completed: false
}
],
{
type: 'ADD_TODO',
payload: { newTodo: { text: 'Use Redux' } }
}
)
).toEqual([
{
text: 'Run the tests',
completed: false
},
{
text: 'Use Redux',
completed: false
}
])
expect(
todosReducer(
[
{
text: 'Run the tests',
completed: false
},
{
text: 'Use Redux',
completed: false
}
],
{
type: 'ADD_TODO',
payload: { newTodo: { text: 'Fix the tests' } }
}
)
).toEqual([
{
text: 'Run the tests',
completed: false
},
{
text: 'Use Redux',
completed: false
},
{
text: 'Fix the tests',
completed: false
}
])
})
it('should handle TOGGLE_TODO', () => {
expect(
todosReducer(
[
{
text: 'Run the tests',
completed: false
},
{
text: 'Use Redux',
completed: false
}
],
{
type: 'TOGGLE_TODO',
payload: { index: 0 }
}
)
).toEqual([
{
text: 'Run the tests',
completed: true
},
{
text: 'Use Redux',
completed: false
}
])
})
}