Merge branch 'feat/inda-383-daily-stat' into 'main'
[ProtonMail-WebClient.git] / applications / drive / src / app / store / _links / useLinksActions.test.tsx
blob492a653a2b69bec0e2a8af05bfb6c9943fc04be1
1 import { renderHook } from '@testing-library/react-hooks';
3 import { RESPONSE_CODE } from '@proton/shared/lib/drive/constants';
4 import isTruthy from '@proton/utils/isTruthy';
6 import { VolumesStateProvider } from '../_volumes/useVolumesState';
7 import { useLinksActions } from './useLinksActions';
9 jest.mock('@proton/components/hooks/usePreventLeave', () => {
10     const usePreventLeave = () => {
11         return {
12             preventLeave: (fn: any) => fn,
13         };
14     };
15     return usePreventLeave;
16 });
18 jest.mock('../_events/useDriveEventManager', () => {
19     const useDriveEventManager = () => {
20         return {
21             pollEvents: {
22                 volumes: () => Promise.resolve(),
23             },
24         };
25     };
26     return {
27         useDriveEventManager,
28     };
29 });
31 jest.mock('./useLinksState', () => {
32     const useLinksState = () => {
33         return {
34             lockLinks: () => {},
35             lockTrash: () => {},
36             unlockLinks: () => {},
37         };
38     };
40     return useLinksState;
41 });
43 jest.mock('../_crypto/useDriveCrypto', () => {
44     const useDriveCrypto = () => {
45         return {
46             getPrimaryAddressKey: () => {},
47             getOwnAddressAndPrimaryKeys: () => {},
48         };
49     };
51     return useDriveCrypto;
52 });
54 jest.mock('./useLink', () => {
55     const useLink = () => {
56         return {
57             getLink: () => {},
58             getLinkPassphraseAndSessionKey: () => {},
59             getLinkPrivateKey: () => {},
60             getLinkHashKey: () => {},
61         };
62     };
64     return useLink;
65 });
67 const mockRequest = jest.fn();
68 const mockGetLinks = jest.fn();
70 const mockQueryTrashLinks = jest.fn();
71 const mockQueryRestoreLinks = jest.fn();
72 const mockQueryDeleteTrashedLinks = jest.fn();
73 const mockGetShare = jest.fn();
74 const mockGetDefaultShare = jest.fn();
76 jest.mock('./useLinks', () => {
77     const useLink = () => {
78         return {
79             getLinks: mockGetLinks,
80         };
81     };
83     return useLink;
84 });
86 jest.mock('../_api/useDebouncedRequest', () => {
87     const useDebouncedRequest = () => {
88         return mockRequest;
89     };
90     return useDebouncedRequest;
91 });
93 jest.mock('../_shares/useShare', () => {
94     const useShare = () => {
95         return {
96             getShare: mockGetShare,
97         };
98     };
99     return useShare;
102 jest.mock('../_shares/useDefaultShare', () => {
103     const useDefaultShare = () => {
104         return {
105             getDefaultShare: mockGetDefaultShare,
106         };
107     };
108     return useDefaultShare;
111 const SHARE_ID_0 = 'shareId00';
112 const SHARE_ID_1 = 'shareId01';
113 const VOLUME_ID = 'volumeId00';
115 // TODO: Test suite incomplete
116 // covers operations allowing using links from multiple shares
117 describe('useLinksActions', () => {
118     let hook: {
119         current: ReturnType<typeof useLinksActions>;
120     };
122     beforeEach(() => {
123         jest.resetAllMocks();
124         mockRequest.mockImplementation((linkIds: string[]) => {
125             return Promise.resolve({
126                 Responses: linkIds.map(() => ({
127                     Response: {
128                         Code: RESPONSE_CODE.SUCCESS,
129                     },
130                 })),
131             });
132         });
133         mockQueryTrashLinks.mockImplementation((shareId, parentLinkId, linkIds) => linkIds);
134         mockQueryRestoreLinks.mockImplementation((shareId, linkIds) => linkIds);
135         mockQueryDeleteTrashedLinks.mockImplementation((shareId, linkIds) => linkIds);
136         mockGetShare.mockImplementation((ac, shareId) => ({ shareId }));
137         mockGetDefaultShare.mockImplementation(() => ({ volumeId: VOLUME_ID }));
139         const wrapper = ({ children }: { children: React.ReactNode }) => (
140             <VolumesStateProvider>{children}</VolumesStateProvider>
141         );
143         const { result } = renderHook(
144             () =>
145                 useLinksActions({
146                     queries: {
147                         queryTrashLinks: mockQueryTrashLinks,
148                         queryRestoreLinks: mockQueryRestoreLinks,
149                         queryDeleteTrashedLinks: mockQueryDeleteTrashedLinks,
150                         queryEmptyTrashOfShare: jest.fn(),
151                         queryDeleteChildrenLinks: jest.fn(),
152                     },
153                 }),
154             { wrapper }
155         );
156         hook = result;
157     });
159     it('trashes links from different shares', async () => {
160         /*
161             shareId00
162             └── link00
163                 ├── link01
164                 │   ├── link03 x
165                 │   └── link04 x
166                 └── link02
167                     └── link05 x
168             shareId01
169             └── link10
170                 ├── link01
171                 |   └── link12 x <-- non-unique parent link id
172                 └── link11
173                     ├── link13
174                     └── link14 x
175         */
177         const ac = new AbortController();
178         const result = await hook.current.trashLinks(ac.signal, [
179             { shareId: SHARE_ID_0, linkId: 'linkId03', parentLinkId: 'linkId01' },
180             { shareId: SHARE_ID_0, linkId: 'linkId04', parentLinkId: 'linkId01' },
181             { shareId: SHARE_ID_0, linkId: 'linkId05', parentLinkId: 'linkId02' },
182             { shareId: SHARE_ID_1, linkId: 'linkId14', parentLinkId: 'linkId11' },
183             { shareId: SHARE_ID_1, linkId: 'linkId12', parentLinkId: 'linkId01' },
184         ]);
186         // ensure api requests are invoked by correct groups
187         expect(mockQueryTrashLinks).toBeCalledWith(SHARE_ID_0, 'linkId01', ['linkId03', 'linkId04']);
188         expect(mockQueryTrashLinks).toBeCalledWith(SHARE_ID_0, 'linkId02', ['linkId05']);
189         expect(mockQueryTrashLinks).toBeCalledWith(SHARE_ID_1, 'linkId01', ['linkId12']);
190         expect(mockQueryTrashLinks).toBeCalledWith(SHARE_ID_1, 'linkId11', ['linkId14']);
192         // verify all requested links were processed
193         expect(result.successes.sort()).toEqual(['linkId03', 'linkId04', 'linkId05', 'linkId12', 'linkId14'].sort());
194     });
196     it('restores links from different shares', async () => {
197         /*
198             shareId00
199             └── link00
200                 ├── linkId01 <
201                 │   ├── linkId03
202                 │   └── linkId04 <
203             shareId01
204             └── linkId10
205                 └── linkId11 <
206         */
208         // emulate partial state
209         const state: Record<string, any> = {
210             linkId01: {
211                 rootShareId: SHARE_ID_0,
212                 linkId: 'linkId01',
213                 trashed: 3,
214             },
215             linkId04: {
216                 rootShareId: SHARE_ID_0,
217                 linkId: 'linkId04',
218                 trashed: 1,
219             },
220             linkId11: {
221                 rootShareId: SHARE_ID_1,
222                 linkId: 'linkId11',
223                 trashed: 3,
224             },
225         };
227         mockGetLinks.mockImplementation(async (signal, ids: { linkId: string }[]) => {
228             return ids.map((idGroup) => state[idGroup.linkId]).filter(isTruthy);
229         });
231         const ac = new AbortController();
232         const result = await hook.current.restoreLinks(ac.signal, [
233             { shareId: SHARE_ID_0, linkId: 'linkId01' },
234             { shareId: SHARE_ID_0, linkId: 'linkId04' },
235             { shareId: SHARE_ID_1, linkId: 'linkId11' },
236         ]);
238         expect(mockQueryRestoreLinks).toBeCalledWith(SHARE_ID_0, [
239             'linkId01',
240             'linkId04', // this link has been deleted before link linkId, thus restored last
241         ]);
242         expect(mockQueryRestoreLinks).toBeCalledWith(SHARE_ID_1, ['linkId11']);
244         expect(result.successes.sort()).toEqual(['linkId01', 'linkId04', 'linkId11'].sort());
245     });
247     it('deletes trashed links from different shares', async () => {
248         /*
249             shareId00
250             └── link00
251                 ├── linkId01 x
252                 │   ├── linkId03
253                 │   └── linkId04 x
254             shareId01
255             └── linkId10
256                 └── linkId11 x
257         */
259         const ac = new AbortController();
260         const result = await hook.current.deleteTrashedLinks(ac.signal, [
261             { shareId: SHARE_ID_0, linkId: 'linkId01' },
262             { shareId: SHARE_ID_0, linkId: 'linkId04' },
263             { shareId: SHARE_ID_1, linkId: 'linkId11' },
264         ]);
266         expect(mockQueryDeleteTrashedLinks).toBeCalledWith(SHARE_ID_0, ['linkId01', 'linkId04']);
267         expect(mockQueryDeleteTrashedLinks).toBeCalledWith(SHARE_ID_1, ['linkId11']);
269         expect(result.successes.sort()).toEqual(['linkId01', 'linkId04', 'linkId11'].sort());
270     });