Merge branch 'feat/inda-383-daily-stat' into 'main'
[ProtonMail-WebClient.git] / packages / hooks / useLoading.test.ts
blobb40060eb4fe81075caf85aa8cfa911f15cae77a0
1 import { act, renderHook } from '@testing-library/react-hooks';
3 import useLoading, { useLoadingByKey } from '@proton/hooks/useLoading';
4 import { createPromise } from '@proton/shared/lib/helpers/promise';
6 describe('useLoading', () => {
7     it('should return loading false by default', () => {
8         const { result } = renderHook(() => useLoading());
9         const [loading] = result.current;
11         expect(loading).toEqual(false);
12     });
14     it('should allow setting of initial loading state', () => {
15         const { result } = renderHook(() => useLoading(true));
16         const [loading] = result.current;
18         expect(loading).toEqual(true);
19     });
21     it('should resolve withLoading if there is no promise', async () => {
22         const { result } = renderHook(() => useLoading());
23         const [loading, withLoading] = result.current;
25         await act(async () => {
26             const resolved = await withLoading(undefined);
27             expect(resolved).toEqual(undefined);
28         });
30         expect(loading).toEqual(false);
31     });
33     it('should resolve promise and returns the result', async () => {
34         const { result } = renderHook(() => useLoading());
35         const [, withLoading] = result.current;
37         await act(async () => {
38             const resolved = await withLoading(Promise.resolve('resolved result'));
40             expect(resolved).toEqual('resolved result');
41         });
43         const [loading] = result.current;
44         expect(loading).toEqual(false);
45     });
47     it('should reject if promise rejects', async () => {
48         const { result } = renderHook(() => useLoading());
49         const [, withLoading] = result.current;
50         const error = new Error('some error');
52         expect.assertions(2);
53         await act(async () => {
54             try {
55                 await withLoading(Promise.reject(error));
56             } catch (e) {
57                 expect(e).toEqual(error);
58             }
59         });
61         const [loading] = result.current;
62         expect(loading).toEqual(false);
63     });
65     it('should render loading state', async () => {
66         const { result } = renderHook(() => useLoading());
67         const [, withLoading] = result.current;
68         const { promise, resolve } = createPromise<void>();
70         let wrappedPromise: Promise<unknown>;
71         act(() => {
72             wrappedPromise = withLoading(promise);
73         });
75         let [loading] = result.current;
76         expect(loading).toEqual(true);
78         await act(async () => {
79             resolve();
80             await wrappedPromise;
81         });
83         [loading] = result.current;
84         expect(loading).toEqual(false);
85     });
87     it('should ignore previous promises', async () => {
88         const { result } = renderHook(() => useLoading());
89         const [, withLoading] = result.current;
91         const { promise: promise1, resolve: resolve1 } = createPromise();
92         const { promise: promise2, resolve: resolve2 } = createPromise();
94         await act(async () => {
95             const wrappedPromise1 = withLoading(promise1);
96             const wrappedPromise2 = withLoading(promise2);
98             resolve1('First result');
99             resolve2('Second result');
101             expect(await wrappedPromise1).toEqual(undefined);
102             expect(await wrappedPromise2).toEqual('Second result');
103         });
104     });
106     it('should ignore previous errors', async () => {
107         const { result } = renderHook(() => useLoading());
108         const [, withLoading] = result.current;
110         const { promise: promise1, reject: reject1 } = createPromise();
111         const { promise: promise2, reject: reject2 } = createPromise();
113         await act(async () => {
114             const wrappedPromise1 = withLoading(promise1);
115             const wrappedPromise2 = withLoading(promise2);
117             let handledFirstReject = false;
118             wrappedPromise1.catch(() => {
119                 handledFirstReject = true;
120             });
122             let handledSecondReject = false;
123             wrappedPromise2.catch(() => {
124                 handledSecondReject = true;
125             });
127             reject1('First reject reason');
128             reject2('Second reject reason');
130             // Handle promises before doing the checks. Switching the control flow in the event loop.
131             expect.assertions(3);
132             await new Promise((resolve) => setTimeout(resolve));
134             expect(handledFirstReject).toEqual(false);
135             expect(handledSecondReject).toEqual(true);
136             expect(await wrappedPromise1).toEqual(undefined);
137         });
138     });
140     it('should support functions as an argument', async () => {
141         const { result } = renderHook(() => useLoading());
142         const [, withLoading] = result.current;
144         async function run(): Promise<string> {
145             return 'resolved result';
146         }
148         await act(async () => {
149             const resolvedResult = await withLoading(run);
151             expect(resolvedResult).toEqual('resolved result');
152         });
153     });
155     it('should expose setLoading', async () => {
156         const { result } = renderHook(() => useLoading());
157         const [, , setLoading] = result.current;
159         act(() => {
160             setLoading(true);
161         });
163         const [loading] = result.current;
164         expect(loading).toBe(true);
165     });
168 describe('useLoadingByKey', () => {
169     describe('when initial state is not provided', () => {
170         it('should return empty map', () => {
171             const { result } = renderHook(() => useLoadingByKey());
172             const [loading] = result.current;
173             expect(loading).toStrictEqual({});
174         });
175     });
177     describe('when initial state is provided', () => {
178         it('should return it at first', () => {
179             const { result } = renderHook(() => useLoadingByKey({ keyA: true }));
180             const [loading] = result.current;
181             expect(loading).toStrictEqual({ keyA: true });
182         });
183     });
185     describe('when promise is undefined', () => {
186         it('should resolve withLoading', async () => {
187             const { result } = renderHook(() => useLoadingByKey());
188             const [, withLoading] = result.current;
190             const resolved = await act(() => withLoading('keyA', undefined));
191             expect(resolved).toEqual(undefined);
193             const [loading] = result.current;
194             expect(loading).toEqual({ keyA: false });
195         });
196     });
198     describe('when promise is defined', () => {
199         it('should resolve promise and returns the result', async () => {
200             const { result } = renderHook(() => useLoadingByKey());
201             const [, withLoading] = result.current;
203             await act(async () => {
204                 const resolved = await withLoading('keyA', Promise.resolve('resolved result'));
205                 expect(resolved).toEqual('resolved result');
206             });
208             const [loading] = result.current;
209             expect(loading).toEqual({ keyA: false });
210         });
212         it('should reject if promise rejects', async () => {
213             const { result } = renderHook(() => useLoadingByKey());
214             const [, withLoading] = result.current;
215             const error = new Error('some error');
217             expect.assertions(2);
218             await act(async () => {
219                 try {
220                     await withLoading('keyA', Promise.reject(error));
221                 } catch (e) {
222                     expect(e).toEqual(error);
223                 }
224             });
226             const [loading] = result.current;
227             expect(loading).toStrictEqual({ keyA: false });
228         });
230         it('should render loading state', async () => {
231             const { result } = renderHook(() => useLoadingByKey());
232             const [, withLoading] = result.current;
233             const { promise, resolve } = createPromise<void>();
235             let wrappedPromise: Promise<unknown>;
236             act(() => {
237                 wrappedPromise = withLoading('keyA', promise);
238             });
240             let [loading] = result.current;
241             expect(loading).toStrictEqual({ keyA: true });
243             await act(async () => {
244                 resolve();
245                 await wrappedPromise;
246             });
248             [loading] = result.current;
249             expect(loading).toStrictEqual({ keyA: false });
250         });
252         it('should ignore previous promises', async () => {
253             const { result } = renderHook(() => useLoadingByKey());
254             const [, withLoading] = result.current;
256             const { promise: promise1, resolve: resolve1 } = createPromise();
257             const { promise: promise2, resolve: resolve2 } = createPromise();
259             await act(async () => {
260                 const wrappedPromise1 = withLoading('keyA', promise1);
261                 const wrappedPromise2 = withLoading('keyA', promise2);
263                 resolve1('First result');
264                 resolve2('Second result');
266                 expect(await wrappedPromise1).toEqual(undefined);
267                 expect(await wrappedPromise2).toEqual('Second result');
268             });
269         });
271         it('should ignore previous errors', async () => {
272             const { result } = renderHook(() => useLoadingByKey());
273             const [, withLoading] = result.current;
275             const { promise: promise1, reject: reject1 } = createPromise();
276             const { promise: promise2, reject: reject2 } = createPromise();
278             await act(async () => {
279                 const wrappedPromise1 = withLoading('keyA', promise1);
280                 const wrappedPromise2 = withLoading('keyA', promise2);
282                 let handledFirstReject = false;
283                 wrappedPromise1.catch(() => {
284                     handledFirstReject = true;
285                 });
287                 let handledSecondReject = false;
288                 wrappedPromise2.catch(() => {
289                     handledSecondReject = true;
290                 });
292                 reject1('First reject reason');
293                 reject2('Second reject reason');
295                 // Handle promises before doing the checks. Switching the control flow in the event loop.
296                 expect.assertions(3);
297                 await new Promise((resolve) => setTimeout(resolve));
299                 expect(handledFirstReject).toEqual(false);
300                 expect(handledSecondReject).toEqual(true);
301                 expect(await wrappedPromise1).toEqual(undefined);
302             });
303         });
305         it('should ignore previous errors', async () => {
306             const { result } = renderHook(() => useLoadingByKey());
307             const [, withLoading] = result.current;
309             const { promise: promise1, reject: reject1 } = createPromise();
310             const { promise: promise2, reject: reject2 } = createPromise();
312             await act(async () => {
313                 const wrappedPromise1 = withLoading('keyA', promise1);
314                 const wrappedPromise2 = withLoading('keyA', promise2);
316                 let handledFirstReject = false;
317                 wrappedPromise1.catch(() => {
318                     handledFirstReject = true;
319                 });
321                 let handledSecondReject = false;
322                 wrappedPromise2.catch(() => {
323                     handledSecondReject = true;
324                 });
326                 reject1('First reject reason');
327                 reject2('Second reject reason');
329                 // Handle promises before doing the checks. Switching the control flow in the event loop.
330                 expect.assertions(3);
331                 await new Promise((resolve) => setTimeout(resolve));
333                 expect(handledFirstReject).toEqual(false);
334                 expect(handledSecondReject).toEqual(true);
335                 expect(await wrappedPromise1).toEqual(undefined);
336             });
337         });
339         it('should support functions as an argument', async () => {
340             const { result } = renderHook(() => useLoadingByKey());
341             const [, withLoading] = result.current;
343             async function run(): Promise<string> {
344                 return 'resolved result';
345             }
347             await act(async () => {
348                 const resolvedResult = await withLoading('keyA', run);
349                 expect(resolvedResult).toEqual('resolved result');
350             });
351         });
353         it('should expose setLoading', async () => {
354             const { result } = renderHook(() => useLoadingByKey());
355             const [, , setLoading] = result.current;
357             act(() => {
358                 setLoading('keyA', true);
359             });
361             const [loading] = result.current;
362             expect(loading).toStrictEqual({ keyA: true });
363         });
364     });
366     describe('when several keys are used', () => {
367         it('should not conflict between each other', async () => {
368             const { result } = renderHook(() => useLoadingByKey());
369             const [, withLoading] = result.current;
371             const { promise: promise1, resolve: resolve1 } = createPromise();
372             const { promise: promise2, resolve: resolve2 } = createPromise();
374             let wrappedPromise1: Promise<unknown>;
375             let wrappedPromise2: Promise<unknown>;
377             await act(async () => {
378                 wrappedPromise1 = withLoading('keyA', promise1);
379             });
381             let [loading] = result.current;
382             expect(loading).toStrictEqual({ keyA: true });
384             await act(async () => {
385                 wrappedPromise2 = withLoading('keyB', promise2);
386             });
388             [loading] = result.current;
389             expect(loading).toStrictEqual({ keyA: true, keyB: true });
391             await act(async () => {
392                 resolve2('Second result');
393                 expect(await wrappedPromise2).toEqual('Second result');
394             });
396             [loading] = result.current;
397             expect(loading).toStrictEqual({ keyA: true, keyB: false });
399             await act(async () => {
400                 resolve1('First result');
401                 expect(await wrappedPromise1).toEqual('First result');
402             });
404             [loading] = result.current;
405             expect(loading).toStrictEqual({ keyA: false, keyB: false });
406         });
407     });