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);
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);
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);
30 expect(loading).toEqual(false);
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');
43 const [loading] = result.current;
44 expect(loading).toEqual(false);
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');
53 await act(async () => {
55 await withLoading(Promise.reject(error));
57 expect(e).toEqual(error);
61 const [loading] = result.current;
62 expect(loading).toEqual(false);
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>;
72 wrappedPromise = withLoading(promise);
75 let [loading] = result.current;
76 expect(loading).toEqual(true);
78 await act(async () => {
83 [loading] = result.current;
84 expect(loading).toEqual(false);
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');
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;
122 let handledSecondReject = false;
123 wrappedPromise2.catch(() => {
124 handledSecondReject = true;
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);
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';
148 await act(async () => {
149 const resolvedResult = await withLoading(run);
151 expect(resolvedResult).toEqual('resolved result');
155 it('should expose setLoading', async () => {
156 const { result } = renderHook(() => useLoading());
157 const [, , setLoading] = result.current;
163 const [loading] = result.current;
164 expect(loading).toBe(true);
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({});
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 });
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 });
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');
208 const [loading] = result.current;
209 expect(loading).toEqual({ keyA: false });
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 () => {
220 await withLoading('keyA', Promise.reject(error));
222 expect(e).toEqual(error);
226 const [loading] = result.current;
227 expect(loading).toStrictEqual({ keyA: false });
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>;
237 wrappedPromise = withLoading('keyA', promise);
240 let [loading] = result.current;
241 expect(loading).toStrictEqual({ keyA: true });
243 await act(async () => {
245 await wrappedPromise;
248 [loading] = result.current;
249 expect(loading).toStrictEqual({ keyA: false });
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');
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;
287 let handledSecondReject = false;
288 wrappedPromise2.catch(() => {
289 handledSecondReject = true;
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);
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;
321 let handledSecondReject = false;
322 wrappedPromise2.catch(() => {
323 handledSecondReject = true;
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);
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';
347 await act(async () => {
348 const resolvedResult = await withLoading('keyA', run);
349 expect(resolvedResult).toEqual('resolved result');
353 it('should expose setLoading', async () => {
354 const { result } = renderHook(() => useLoadingByKey());
355 const [, , setLoading] = result.current;
358 setLoading('keyA', true);
361 const [loading] = result.current;
362 expect(loading).toStrictEqual({ keyA: true });
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);
381 let [loading] = result.current;
382 expect(loading).toStrictEqual({ keyA: true });
384 await act(async () => {
385 wrappedPromise2 = withLoading('keyB', promise2);
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');
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');
404 [loading] = result.current;
405 expect(loading).toStrictEqual({ keyA: false, keyB: false });