1 | import { act, renderHook } from '@testing-library/react-hooks';
|
2 | import { useReducer } from 'react';
|
3 | import { reducer, init } from './product-list-reducer';
|
4 | import { LocalStorageMock } from '@react-mock/localstorage';
|
5 | import { render } from '@testing-library/react';
|
6 | import React from 'react';
|
7 |
|
8 | const 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 |
|
45 | describe('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 |
|
82 | describe('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 |
|
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 |
|
387 | function setup(initialState) {
|
388 | const { result } = renderHook(() => useProductListReducer({ initialState }));
|
389 |
|
390 | const [, dispatch] = result.current;
|
391 |
|
392 | return { result, dispatch };
|
393 | }
|
394 |
|
395 | function useProductListReducer({ initialState }) {
|
396 | const [state, dispatch] = useReducer(
|
397 | reducer,
|
398 | initialState || { loggedIn: false, list: {} }
|
399 | );
|
400 |
|
401 | return [state, dispatch];
|
402 | }
|