Skip to content

Commit 6e73961

Browse files
committed
Add a test that combines slices, async thunks, and entities
1 parent da2fad9 commit 6e73961

File tree

1 file changed

+109
-0
lines changed

1 file changed

+109
-0
lines changed

src/combinedTest.test.ts

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import { createAsyncThunk } from './createAsyncThunk'
2+
import { createAction, PayloadAction } from './createAction'
3+
import { createSlice } from './createSlice'
4+
import { configureStore } from './configureStore'
5+
import { createEntityAdapter } from './entities/create_adapter'
6+
import { EntityAdapter } from './entities/models'
7+
import { BookModel } from './entities/fixtures/book'
8+
9+
describe('Combined entity slice', () => {
10+
let adapter: EntityAdapter<BookModel>
11+
12+
beforeEach(() => {
13+
adapter = createEntityAdapter({
14+
selectId: (book: BookModel) => book.id,
15+
sortComparer: (a, b) => a.title.localeCompare(b.title)
16+
})
17+
})
18+
19+
it('Entity and async features all works together', async () => {
20+
const upsertBook = createAction<BookModel>('otherBooks/upsert')
21+
22+
type BooksState = ReturnType<typeof adapter.getInitialState> & {
23+
loading: 'initial' | 'pending' | 'finished' | 'failed'
24+
lastRequestId: string | null
25+
}
26+
27+
const initialState: BooksState = adapter.getInitialState({
28+
loading: 'initial',
29+
lastRequestId: null
30+
})
31+
32+
const fakeBooks: BookModel[] = [
33+
{ id: 'b', title: 'Second' },
34+
{ id: 'a', title: 'First' }
35+
]
36+
37+
const fetchBooksTAC = createAsyncThunk('books/fetch', async () => {
38+
return fakeBooks
39+
})
40+
41+
const booksSlice = createSlice({
42+
name: 'books',
43+
initialState,
44+
reducers: {
45+
addOne: adapter.addOne,
46+
removeOne(state, action: PayloadAction<string>) {
47+
// TODO The nested `produce` calls don't mutate `state` here as I would have expected.
48+
// TODO (note that `state` here is actually an Immer Draft<S>, from `createReducer`)
49+
// TODO However, this works if we _return_ the new plain result value instead
50+
// TODO See https://github.com/immerjs/immer/issues/533
51+
const result = adapter.removeOne(state, action)
52+
return result
53+
}
54+
},
55+
extraReducers: builder => {
56+
builder.addCase(upsertBook, (state, action) => {
57+
return adapter.upsertOne(state, action)
58+
})
59+
builder.addCase(fetchBooksTAC.pending, (state, action) => {
60+
state.loading = 'pending'
61+
state.lastRequestId = action.meta.requestId
62+
})
63+
builder.addCase(fetchBooksTAC.fulfilled, (state, action) => {
64+
if (
65+
state.loading === 'pending' &&
66+
action.meta.requestId === state.lastRequestId
67+
) {
68+
return {
69+
...adapter.setAll(state, action.payload),
70+
loading: 'finished',
71+
lastRequestId: null
72+
}
73+
}
74+
})
75+
}
76+
})
77+
78+
const { addOne, removeOne } = booksSlice.actions
79+
const { reducer } = booksSlice
80+
81+
const store = configureStore({
82+
reducer: {
83+
books: reducer
84+
}
85+
})
86+
87+
await store.dispatch(fetchBooksTAC())
88+
89+
const { books: booksAfterLoaded } = store.getState()
90+
// Sorted, so "First" goes first
91+
expect(booksAfterLoaded.ids).toEqual(['a', 'b'])
92+
expect(booksAfterLoaded.lastRequestId).toBe(null)
93+
expect(booksAfterLoaded.loading).toBe('finished')
94+
95+
store.dispatch(addOne({ id: 'c', title: 'Middle' }))
96+
97+
const { books: booksAfterAddOne } = store.getState()
98+
99+
// Sorted, so "Middle" goes in the middle
100+
expect(booksAfterAddOne.ids).toEqual(['a', 'c', 'b'])
101+
102+
store.dispatch(upsertBook({ id: 'c', title: 'Zeroth' }))
103+
104+
const { books: booksAfterUpsert } = store.getState()
105+
106+
// Sorted, so "Zeroth" goes last
107+
expect(booksAfterUpsert.ids).toEqual(['a', 'b', 'c'])
108+
})
109+
})

0 commit comments

Comments
 (0)