Update selected item color in Pass menu
[ProtonMail-WebClient.git] / packages / pass / hooks / useActionRequest.spec.tsx
blob727b00efa6da87809040dea80b9a67be695f57d0
1 import type { PropsWithChildren, ReactNode } from 'react';
2 import { Provider as ReduxProvider } from 'react-redux';
4 import { type UnknownAction, configureStore, createAction } from '@reduxjs/toolkit';
5 import { act, renderHook } from '@testing-library/react-hooks';
7 import { withRequest } from '@proton/pass/store/request/enhancers';
8 import { requestMiddleware } from '@proton/pass/store/request/middleware';
9 import request from '@proton/pass/store/request/reducer';
11 import { useActionRequest } from './useActionRequest';
13 const requestId = 'test::id';
15 const start = createAction('test::start', () => withRequest({ status: 'start', id: requestId })({ payload: {} }));
16 const success = createAction('test::success', () => withRequest({ status: 'success', id: requestId })({ payload: {} }));
17 const failure = createAction('test::failure', () => withRequest({ status: 'failure', id: requestId })({ payload: {} }));
19 const successCache = createAction('test::success::cache', () =>
20     withRequest({ status: 'success', id: requestId, maxAge: 1 })({ payload: {} })
23 const buildHook = (useInitialRequestId: boolean = true, initialActions: UnknownAction[] = []) => {
24     const store = configureStore({ reducer: { request }, middleware: (mw) => mw().concat(requestMiddleware) });
25     initialActions.forEach((action) => store.dispatch(action));
27     const onStart = jest.fn();
28     const onSuccess = jest.fn();
29     const onFailure = jest.fn();
31     return {
32         store,
33         onStart,
34         onSuccess,
35         onFailure,
36         hook: renderHook<PropsWithChildren, ReturnType<typeof useActionRequest>>(
37             () =>
38                 useActionRequest(start, {
39                     initialRequestId: useInitialRequestId ? requestId : undefined,
40                     onStart,
41                     onSuccess,
42                     onFailure,
43                 }),
44             {
45                 wrapper: (({ children }: { children: ReactNode }) => (
46                     <ReduxProvider store={store}>{children}</ReduxProvider>
47                 )) as any,
48             }
49         ),
50     };
53 describe('useActionRequest', () => {
54     it('Handles request sequence', async () => {
55         const { hook, onStart, onSuccess, onFailure, store } = buildHook();
57         expect(hook.result.current.loading).toBe(false);
58         expect(hook.result.current.progress).toEqual(0);
59         expect(onStart).not.toHaveBeenCalled();
60         expect(onSuccess).not.toHaveBeenCalled();
61         expect(onFailure).not.toHaveBeenCalled();
63         act(() => {
64             hook.result.current.dispatch();
65         });
67         expect(hook.result.current.loading).toBe(true);
68         expect(hook.result.current.progress).toEqual(0);
69         expect(onStart).toHaveBeenCalledTimes(1);
70         expect(onSuccess).not.toHaveBeenCalled();
71         expect(onFailure).not.toHaveBeenCalled();
73         act(() => {
74             store.dispatch(success());
75         });
77         expect(hook.result.current.loading).toBe(false);
78         expect(hook.result.current.progress).toBe(100);
79         expect(onStart).toHaveBeenCalledTimes(1);
80         expect(onSuccess).toHaveBeenCalledTimes(1);
81         expect(onFailure).not.toHaveBeenCalled();
83         act(() => {
84             hook.result.current.dispatch();
85         });
87         expect(hook.result.current.loading).toBe(true);
88         expect(hook.result.current.progress).toEqual(0);
89         expect(onStart).toHaveBeenCalledTimes(2);
90         expect(onSuccess).toHaveBeenCalledTimes(1);
91         expect(onFailure).not.toHaveBeenCalled();
93         act(() => {
94             store.dispatch(failure());
95         });
97         expect(hook.result.current.loading).toBe(false);
98         expect(hook.result.current.progress).toBe(100);
99         expect(onStart).toHaveBeenCalledTimes(2);
100         expect(onSuccess).toHaveBeenCalledTimes(1);
101         expect(onFailure).toHaveBeenCalledTimes(1);
102     });
104     test('Revalidate should re-trigger sequence', () => {
105         const { hook, onStart, onSuccess, onFailure } = buildHook(true, [successCache()]);
107         act(() => {
108             hook.result.current.revalidate();
109         });
111         expect(hook.result.current.loading).toBe(true);
112         expect(hook.result.current.progress).toEqual(0);
113         expect(onStart).toHaveBeenCalledTimes(1);
114         expect(onSuccess).toHaveBeenCalledTimes(1);
115         expect(onFailure).not.toHaveBeenCalled();
116     });
118     test('dispatching should noop if request success with maxAge', () => {
119         const { hook, onStart, onSuccess, onFailure } = buildHook(false, [successCache()]);
121         act(() => {
122             hook.result.current.dispatch();
123         });
125         expect(hook.result.current.loading).toBe(false);
126         expect(hook.result.current.progress).toEqual(100);
127         expect(onStart).not.toHaveBeenCalled();
128         expect(onSuccess).toHaveBeenCalledTimes(1);
129         expect(onFailure).not.toHaveBeenCalled();
130     });
132     test('dispatching the same action should only trigger effect once', () => {
133         const { hook, onStart, onSuccess, onFailure } = buildHook(false, []);
135         act(() => {
136             hook.result.current.dispatch();
137             hook.result.current.dispatch();
138             hook.result.current.dispatch();
139             hook.result.current.dispatch();
140         });
142         expect(hook.result.current.loading).toBe(true);
143         expect(hook.result.current.progress).toEqual(0);
144         expect(onStart).toHaveBeenCalledTimes(1);
145         expect(onSuccess).not.toHaveBeenCalled();
146         expect(onFailure).not.toHaveBeenCalled();
147     });
149     test('Setting `initialRequestId` should trigger `onStart`', () => {
150         const { hook, onStart, onSuccess, onFailure } = buildHook(true, [start()]);
152         expect(hook.result.current.loading).toBe(true);
153         expect(hook.result.current.progress).toEqual(0);
154         expect(onStart).toHaveBeenCalledTimes(1);
155         expect(onSuccess).not.toHaveBeenCalled();
156         expect(onFailure).not.toHaveBeenCalled();
157     });
159     test('Setting `initialRequestId` should trigger `onSuccess`', () => {
160         const { hook, onStart, onSuccess, onFailure } = buildHook(true, [success()]);
162         expect(hook.result.current.loading).toBe(false);
163         expect(hook.result.current.progress).toEqual(100);
164         expect(onStart).not.toHaveBeenCalled();
165         expect(onSuccess).toHaveBeenCalledTimes(1);
166         expect(onFailure).not.toHaveBeenCalled();
167     });
169     test('Setting `initialRequestId` should trigger `onFailure`', () => {
170         const { hook, onStart, onSuccess, onFailure } = buildHook(true, [failure()]);
172         expect(hook.result.current.loading).toBe(false);
173         expect(hook.result.current.progress).toEqual(100);
174         expect(onStart).not.toHaveBeenCalled();
175         expect(onSuccess).not.toHaveBeenCalled();
176         expect(onFailure).toHaveBeenCalledTimes(1);
177     });