source: node_modules/@reduxjs/toolkit/src/tests/combineSlices.test.ts@ ba17441

Last change on this file since ba17441 was a762898, checked in by istevanoska <ilinastevanoska@…>, 5 months ago

Added visualizations

  • Property mode set to 100644
File size: 6.2 KB
Line 
1import type { WithSlice } from '@reduxjs/toolkit'
2import {
3 combineSlices,
4 createAction,
5 createReducer,
6 createSlice,
7} from '@reduxjs/toolkit'
8
9const dummyAction = createAction<void>('dummy')
10
11const stringSlice = createSlice({
12 name: 'string',
13 initialState: '',
14 reducers: {},
15})
16
17const numberSlice = createSlice({
18 name: 'number',
19 initialState: 0,
20 reducers: {},
21})
22
23const booleanReducer = createReducer(false, () => {})
24
25const counterReducer = createSlice({
26 name: 'counter',
27 initialState: () => ({ value: 0 }),
28 reducers: {},
29})
30
31// mimic - we can't use RTKQ here directly
32const api = {
33 reducerPath: 'api' as const,
34 reducer: createReducer(
35 {
36 queries: {},
37 mutations: {},
38 provided: {},
39 subscriptions: {},
40 config: {
41 reducerPath: 'api',
42 invalidationBehavior: 'delayed',
43 online: false,
44 focused: false,
45 keepUnusedDataFor: 60,
46 middlewareRegistered: false,
47 refetchOnMountOrArgChange: false,
48 refetchOnReconnect: false,
49 refetchOnFocus: false,
50 },
51 },
52 () => {},
53 ),
54}
55
56describe('combineSlices', () => {
57 it('calls combineReducers to combine static slices/reducers', () => {
58 const combinedReducer = combineSlices(
59 stringSlice,
60 {
61 num: numberSlice.reducer,
62 boolean: booleanReducer,
63 },
64 api,
65 )
66 expect(combinedReducer(undefined, dummyAction())).toEqual({
67 string: stringSlice.getInitialState(),
68 num: numberSlice.getInitialState(),
69 boolean: booleanReducer.getInitialState(),
70 api: api.reducer.getInitialState(),
71 })
72 })
73 it('allows passing no initial reducers', () => {
74 const combinedReducer = combineSlices()
75
76 const result = combinedReducer(undefined, dummyAction())
77
78 expect(result).toEqual({})
79
80 // no-op if we have no reducers yet
81 expect(combinedReducer(result, dummyAction())).toBe(result)
82 })
83 describe('injects', () => {
84 beforeEach(() => {
85 vi.stubEnv('NODE_ENV', 'development')
86
87 return vi.unstubAllEnvs
88 })
89
90 it('injects slice', () => {
91 const combinedReducer =
92 combineSlices(stringSlice).withLazyLoadedSlices<
93 WithSlice<typeof numberSlice>
94 >()
95
96 expect(combinedReducer(undefined, dummyAction()).number).toBe(undefined)
97
98 const injectedReducer = combinedReducer.inject(numberSlice)
99
100 expect(injectedReducer(undefined, dummyAction()).number).toBe(
101 numberSlice.getInitialState(),
102 )
103 })
104 it('logs error when same name is used for different reducers', () => {
105 const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
106 const combinedReducer = combineSlices(stringSlice).withLazyLoadedSlices<{
107 boolean: boolean
108 }>()
109
110 combinedReducer.inject({
111 reducerPath: 'boolean' as const,
112 reducer: booleanReducer,
113 })
114
115 combinedReducer.inject({
116 reducerPath: 'boolean' as const,
117 reducer: booleanReducer,
118 })
119
120 expect(consoleSpy).not.toHaveBeenCalled()
121
122 combinedReducer.inject({
123 reducerPath: 'boolean' as const,
124 // @ts-expect-error wrong reducer
125 reducer: stringSlice.reducer,
126 })
127
128 expect(consoleSpy).toHaveBeenCalledWith(
129 `called \`inject\` to override already-existing reducer boolean without specifying \`overrideExisting: true\``,
130 )
131 consoleSpy.mockRestore()
132 })
133 it('allows replacement of reducers if overrideExisting is true', () => {
134 const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
135 const combinedReducer = combineSlices(stringSlice).withLazyLoadedSlices<
136 WithSlice<typeof numberSlice> &
137 WithSlice<typeof api> & { boolean: boolean }
138 >()
139
140 combinedReducer.inject(numberSlice)
141
142 combinedReducer.inject(
143 { reducerPath: 'number' as const, reducer: () => 0 },
144 { overrideExisting: true },
145 )
146
147 expect(consoleSpy).not.toHaveBeenCalled()
148 })
149 })
150 describe('selector', () => {
151 const combinedReducer = combineSlices(stringSlice).withLazyLoadedSlices<{
152 boolean: boolean
153 counter: { value: number }
154 }>()
155
156 const uninjectedState = combinedReducer(undefined, dummyAction())
157
158 const injectedReducer = combinedReducer.inject({
159 reducerPath: 'boolean' as const,
160 reducer: booleanReducer,
161 })
162
163 it('ensures state is defined in selector even if action has not been dispatched', () => {
164 expect(uninjectedState.boolean).toBe(undefined)
165
166 const selectBoolean = injectedReducer.selector((state) => state.boolean)
167
168 expect(selectBoolean(uninjectedState)).toBe(
169 booleanReducer.getInitialState(),
170 )
171 })
172 it('exposes original to allow for logging', () => {
173 const selectBoolean = injectedReducer.selector(
174 (state) => injectedReducer.selector.original(state).boolean,
175 )
176 expect(selectBoolean(uninjectedState)).toBe(undefined)
177 })
178 it('throws if original is called on something other than state proxy', () => {
179 expect(() => injectedReducer.selector.original({} as any)).toThrow(
180 'original must be used on state Proxy',
181 )
182 })
183 it('allows passing a selectState selector, to handle nested state', () => {
184 const wrappedReducer = combineSlices({
185 inner: combinedReducer,
186 })
187
188 type RootState = ReturnType<typeof wrappedReducer>
189
190 const selector = injectedReducer.selector(
191 (state) => state.boolean,
192 (rootState: RootState) => rootState.inner,
193 )
194
195 expect(selector(wrappedReducer(undefined, dummyAction()))).toBe(
196 booleanReducer.getInitialState(),
197 )
198 })
199 it('caches initial state', () => {
200 const beforeInject = combinedReducer(undefined, dummyAction())
201 const injectedReducer = combinedReducer.inject(counterReducer)
202 const selectCounter = injectedReducer.selector((state) => state.counter)
203 const counter = selectCounter(beforeInject)
204 expect(counter).toBe(selectCounter(beforeInject))
205
206 injectedReducer.inject(
207 { reducerPath: 'counter', reducer: () => ({ value: 0 }) },
208 { overrideExisting: true },
209 )
210 const counter2 = selectCounter(beforeInject)
211 expect(counter2).not.toBe(counter)
212 expect(counter2).toBe(selectCounter(beforeInject))
213 })
214 })
215})
Note: See TracBrowser for help on using the repository browser.