UNPKG

9.73 kBJavaScriptView Raw
1import { act, renderHook } from '@testing-library/react-hooks';
2import { useReducer } from 'react';
3import { reducer, init } from './product-list-reducer';
4import { LocalStorageMock } from '@react-mock/localstorage';
5import { render } from '@testing-library/react';
6import React from 'react';
7
8const MOCK_LIST = {
9 '6952315': {
10 options: { quantity: 1 },
11 variants: {
12 '6952315-170-white': {
13 options: {
14 quantity: 1
15 },
16 parentArticleNumber: '6952315'
17 }
18 }
19 },
20 '502935480': {
21 variants: null,
22 options: {
23 quantity: 1
24 }
25 },
26 '441922-Classic-16': {
27 variants: null,
28 options: {
29 quantity: 1
30 }
31 },
32 'without-base': {
33 options: null,
34 variants: {
35 'with-variant': {
36 options: {
37 quantity: 1
38 },
39 parentArticleNumber: 'without-base'
40 }
41 }
42 }
43};
44
45describe('reducer init method', () => {
46 it('defaults the list to localStorage', () => {
47 const obj = {
48 key: 'value'
49 };
50
51 function Parent({ children }) {
52 const mockList = JSON.stringify(obj);
53 return (
54 <LocalStorageMock items={{ productList: mockList }}>
55 {children}
56 </LocalStorageMock>
57 );
58 }
59 function Child() {
60 const initialState = { loggedIn: false, list: {} };
61 const state = init(initialState);
62
63 return (
64 <div>
65 <span>{state.list.key}</span>
66 <span>{state.loggedIn ? 'logged in' : 'not logged in'}</span>
67 </div>
68 );
69 }
70
71 const { getByText } = render(
72 <Parent>
73 <Child />
74 </Parent>
75 );
76
77 expect(getByText('not logged in'));
78 expect(getByText('value'));
79 });
80});
81
82describe('reducer', () => {
83 it('sets logged in and payload when LOGIN is called', () => {
84 const { result, dispatch } = setup();
85 const payload = { test: 'one' };
86
87 act(() => dispatch({ type: 'LOGIN', payload }));
88
89 const [state] = result.current;
90
91 expect(state.loggedIn).toBe(true);
92 expect(state.list).toBe(payload);
93 });
94 it('sets loggedIn and resets list on LOGOUT', () => {
95 const initialState = {
96 loggedIn: true,
97 list: { '234523423': { options: { quantity: 1, description: '' } } }
98 };
99
100 const { result, dispatch } = setup(initialState);
101
102 {
103 const [state] = result.current;
104 expect(state).toBe(initialState);
105 }
106
107 act(() => dispatch({ type: 'LOGOUT' }));
108
109 const [state] = result.current;
110
111 expect(state.loggedIn).toBe(false);
112 expect(state.list).toMatchObject({});
113 });
114 describe('UPDATE action', () => {
115 it('can replace a base product with one of its variants', () => {
116 const initialState = {
117 loggedIn: false,
118 list: MOCK_LIST
119 };
120
121 const { result, dispatch } = setup(initialState);
122
123 act(() =>
124 dispatch({
125 type: 'UPDATE',
126 payload: {
127 articleNumber: '502935480',
128 variantArticleNumber: '502935480-sparkles',
129 options: {
130 quantity: 1
131 }
132 }
133 })
134 );
135
136 const [state] = result.current;
137
138 const baseProduct = state.list['502935480'];
139
140 expect(baseProduct.variants['502935480-sparkles'].options.quantity).toBe(
141 1
142 );
143 // Removing the base product options effectively removes it from the list
144 expect(baseProduct.options).toBe(null);
145 });
146 it('can replace a variant with another variant', () => {
147 const initialState = {
148 loggedIn: false,
149 list: MOCK_LIST
150 };
151
152 const { result, dispatch } = setup(initialState);
153
154 act(() =>
155 dispatch({
156 type: 'UPDATE',
157 payload: {
158 articleNumber: '6952315',
159 variantArticleNumber: '6952315-170-pink',
160 options: {
161 quantity: 1
162 },
163 variantToReplace: '6952315-170-white'
164 }
165 })
166 );
167
168 const [state] = result.current;
169
170 expect(
171 state.list['6952315'].variants['6952315-170-pink'].options.quantity
172 ).toBe(1);
173 expect(
174 state.list['6952315'].variants['6952315-170-white']
175 ).not.toBeDefined();
176 });
177 });
178 describe('ADD action', () => {
179 it('can add a base product to the list', () => {
180 const initialState = {
181 loggedIn: false,
182 list: MOCK_LIST
183 };
184
185 const { result, dispatch } = setup(initialState);
186
187 act(() =>
188 dispatch({
189 type: 'ADD',
190 payload: {
191 articleNumber: 'boogie',
192 options: {
193 quantity: 1
194 }
195 }
196 })
197 );
198
199 const [state] = result.current;
200
201 expect(state.list['boogie'].options.quantity).toBe(1);
202 });
203 it('can add a variant to the list when there is no existing base product', () => {
204 const initialState = {
205 loggedIn: false,
206 list: MOCK_LIST
207 };
208
209 const { result, dispatch } = setup(initialState);
210
211 act(() =>
212 dispatch({
213 type: 'ADD',
214 payload: {
215 articleNumber: 'boogie',
216 variantArticleNumber: 'boogie-woogie',
217 options: {
218 quantity: 1
219 }
220 }
221 })
222 );
223
224 const [state] = result.current;
225
226 expect(state.list['boogie'].options).toBe(null);
227 expect(
228 state.list['boogie'].variants['boogie-woogie'].options.quantity
229 ).toBe(1);
230 });
231 it('can add a variant to the list when there is an existing base product', () => {
232 const initialState = {
233 loggedIn: false,
234 list: MOCK_LIST
235 };
236
237 const { result, dispatch } = setup(initialState);
238
239 act(() =>
240 dispatch({
241 type: 'ADD',
242 payload: {
243 articleNumber: '502935480',
244 variantArticleNumber: 'boogie-woogie',
245 options: {
246 quantity: 1
247 }
248 }
249 })
250 );
251
252 const [state] = result.current;
253
254 expect(state.list['502935480'].options).not.toBe(null);
255 expect(
256 state.list['502935480'].variants['boogie-woogie'].options.quantity
257 ).toBe(1);
258 });
259 it('can add a variant to the list when there is a base product with variants', () => {
260 const initialState = {
261 loggedIn: false,
262 list: MOCK_LIST
263 };
264
265 const { result, dispatch } = setup(initialState);
266
267 act(() =>
268 dispatch({
269 type: 'ADD',
270 payload: {
271 articleNumber: '6952315',
272 variantArticleNumber: 'boogie-woogie',
273 options: {
274 quantity: 1
275 }
276 }
277 })
278 );
279
280 const [state] = result.current;
281
282 expect(state.list['6952315'].options).not.toBe(null);
283 expect(
284 state.list['6952315'].variants['boogie-woogie'].options.quantity
285 ).toBe(1);
286 expect(state.list['6952315'].variants['6952315-170-white']).toMatchObject(
287 MOCK_LIST['6952315'].variants['6952315-170-white']
288 );
289 });
290 });
291 describe('REMOVE action', () => {
292 it('will entirely remove a base product with no variants', () => {
293 const initialState = {
294 loggedIn: false,
295 list: MOCK_LIST
296 };
297
298 const { result, dispatch } = setup(initialState);
299
300 act(() =>
301 dispatch({
302 type: 'REMOVE',
303 payload: {
304 articleNumber: '502935480'
305 }
306 })
307 );
308
309 const [state] = result.current;
310
311 expect(state.list['502935480']).not.toBeDefined();
312 });
313 it('will entirely remove an item if the variant is removed and there is no base product', () => {
314 const initialState = {
315 loggedIn: false,
316 list: MOCK_LIST
317 };
318
319 const { result, dispatch } = setup(initialState);
320
321 act(() =>
322 dispatch({
323 type: 'REMOVE',
324 payload: {
325 articleNumber: 'without-base',
326 variantArticleNumber: 'with-variant'
327 }
328 })
329 );
330
331 const [state] = result.current;
332
333 expect(state.list['without-base']).not.toBeDefined();
334 });
335 it('will keep the variant if one exists', () => {
336 const initialState = {
337 loggedIn: false,
338 list: MOCK_LIST
339 };
340
341 const { result, dispatch } = setup(initialState);
342 const articleToRemove = '6952315';
343
344 act(() =>
345 dispatch({
346 type: 'REMOVE',
347 payload: {
348 articleNumber: articleToRemove
349 }
350 })
351 );
352
353 const [state] = result.current;
354
355 expect(state.list[articleToRemove].options).toBe(null);
356 expect(state.list[articleToRemove].variants).toBeDefined();
357 });
358 it('will remove the variant but keep the base product', () => {
359 const initialState = {
360 loggedIn: false,
361 list: MOCK_LIST
362 };
363
364 const { result, dispatch } = setup(initialState);
365
366 const parent = '6952315';
367 const articleToRemove = '6952315-170-white';
368
369 act(() =>
370 dispatch({
371 type: 'REMOVE',
372 payload: {
373 articleNumber: parent,
374 variantArticleNumber: articleToRemove
375 }
376 })
377 );
378
379 const [state] = result.current;
380
381 expect(state.list[parent].variants[articleToRemove]).not.toBeDefined();
382 expect(state.list[parent].options).toBeDefined();
383 });
384 });
385});
386
387function setup(initialState) {
388 const { result } = renderHook(() => useProductListReducer({ initialState }));
389
390 const [, dispatch] = result.current;
391
392 return { result, dispatch };
393}
394
395function useProductListReducer({ initialState }) {
396 const [state, dispatch] = useReducer(
397 reducer,
398 initialState || { loggedIn: false, list: {} }
399 );
400
401 return [state, dispatch];
402}