Merge branch 'feat/inda-383-daily-stat' into 'main'
[ProtonMail-WebClient.git] / applications / drive / src / app / components / modals / ShareLinkModal / DirectSharing / useShareInvitees.test.ts
blob85447066a3b2a4173038d52ade8370badacfbada
1 import { act, renderHook } from '@testing-library/react-hooks';
2 import { verifyAllWhenMocksCalled, when } from 'jest-when';
4 import { useNotifications } from '@proton/components';
5 import type { PublicKeyReference } from '@proton/crypto';
6 import { CryptoProxy } from '@proton/crypto';
7 import noop from '@proton/utils/noop';
9 import { useGetPublicKeysForEmail } from '../../../../../app/store';
10 import { ShareInviteeValidationError, VALIDATION_ERROR_TYPES } from './helpers/ShareInviteeValidationError';
11 import { useShareInvitees } from './useShareInvitees';
13 // Internal and proton account
14 const inviteeInternal = {
15     name: 'bob@proton.black',
16     email: 'bob@proton.black',
19 // Internal and proton account
20 const inviteeInternal2 = {
21     name: 'alice@proton.black',
22     email: 'alice@proton.black',
25 // External account but proton one
26 const inviteeExternalProton = {
27     name: 'jack@proton.me',
28     email: 'jack@proton.me',
31 // External non-proton account
32 const inviteeExternalNonProton = {
33     name: 'rob@jon.foo',
34     email: 'rob@jon.foo',
37 const mockedCreateNotification = jest.fn();
38 jest.mock('@proton/components', () => ({
39     useGetEncryptionPreferences: jest.fn(),
40     useNotifications: jest.fn(),
41     useApi: jest.fn().mockReturnValue(() => new Promise((resolve) => resolve(undefined))),
42 }));
44 jest.mocked(useNotifications).mockReturnValue({
45     createNotification: mockedCreateNotification,
46 } as any);
48 jest.mock('@proton/crypto');
49 const mockedImportPublicKey = jest.mocked(CryptoProxy.importPublicKey);
51 jest.mock('../../../../store/_user/useGetPublicKeysForEmail');
52 const mockedGetPrimaryPublicKey = jest.fn();
53 jest.mocked(useGetPublicKeysForEmail).mockReturnValue({
54     getPrimaryPublicKeyForEmail: mockedGetPrimaryPublicKey,
55     getPublicKeysForEmail: jest.fn(),
56 });
58 jest.mock('../../../../store/_shares/useDriveSharingFlags', () => ({
59     useDriveSharingFlags: jest
60         .fn()
61         .mockReturnValue({ isSharingExternalInviteDisabled: false, isSharingExternalInviteAvailable: true }),
62 }));
64 const primaryPublicKey = 'primaryPublicKey';
65 const publicKey = {
66     _idx: 1868513808,
67     _keyContentHash: ['dashdhuiwy323213dsahjeg123g21312', '123heiuwqhdqighdy1t223813y2weaseguy'],
68     subkeys: [{}],
69 } as PublicKeyReference;
71 describe('useShareInvitees', () => {
72     beforeEach(() => {
73         when(mockedGetPrimaryPublicKey)
74             .calledWith(inviteeInternal.email, new AbortController().signal)
75             .mockResolvedValue(primaryPublicKey);
77         when(mockedImportPublicKey).calledWith({ armoredKey: primaryPublicKey }).mockResolvedValue(publicKey);
78     });
79     afterEach(() => {
80         jest.clearAllMocks();
81     });
83     describe('add', () => {
84         it('should be able to add proton/non-proton invitee', async () => {
85             const { result } = renderHook(() => useShareInvitees([]));
86             when(mockedGetPrimaryPublicKey)
87                 .calledWith(inviteeInternal2.email, new AbortController().signal)
88                 .mockResolvedValueOnce(undefined);
90             await act(async () => {
91                 void result.current
92                     .add([inviteeInternal, inviteeInternal2, inviteeExternalProton, inviteeExternalNonProton])
93                     .catch(noop);
94             });
95             // @ts-ignore
96             expect(result.all[1].invitees).toEqual([
97                 { ...inviteeInternal, isLoading: true },
98                 { ...inviteeInternal2, isLoading: true },
99                 { ...inviteeExternalProton, isLoading: true },
100                 { ...inviteeExternalNonProton, isLoading: true },
101             ]);
103             expect(result.current.invitees).toEqual([
104                 { ...inviteeInternal, publicKey, isExternal: false, isLoading: false },
105                 {
106                     ...inviteeInternal2,
107                     isExternal: true,
108                     error: undefined,
109                     isLoading: false,
110                 },
111                 {
112                     ...inviteeExternalProton,
113                     error: undefined,
114                     isExternal: true,
115                     isLoading: false,
116                 },
117                 {
118                     ...inviteeExternalNonProton,
119                     error: undefined,
120                     isExternal: true,
121                     isLoading: false,
122                 },
123             ]);
124             verifyAllWhenMocksCalled();
125         });
127         it('should show duplicate notification', async () => {
128             const { result, waitFor } = renderHook(() => useShareInvitees([]));
130             act(() => {
131                 result.current.add([inviteeInternal, inviteeInternal]).catch(noop);
132             });
134             await waitFor(() =>
135                 expect(result.current.invitees).toEqual([
136                     { ...inviteeInternal, publicKey, isExternal: false, isLoading: false, error: undefined },
137                 ])
138             );
139             expect(mockedCreateNotification).toHaveBeenCalledWith({
140                 type: 'warning',
141                 text: `Removed duplicate invitees: ${inviteeInternal.email}`,
142             });
143         });
145         it('adding an existing invitee should add it with an error', async () => {
146             const { result, waitFor } = renderHook(() => useShareInvitees([inviteeInternal.email]));
148             act(() => {
149                 result.current.add([inviteeInternal]).catch(noop);
150             });
152             await waitFor(() =>
153                 expect(result.current.invitees).toEqual([
154                     {
155                         ...inviteeInternal,
156                         error: new ShareInviteeValidationError(VALIDATION_ERROR_TYPES.EXISTING_MEMBER),
157                     },
158                 ])
159             );
160         });
162         it('adding an invitee with bad email should add it with an error', async () => {
163             const { result, waitFor } = renderHook(() => useShareInvitees([]));
165             act(() => {
166                 result.current.add([{ ...inviteeInternal, email: 'lucienTest.com' }]).catch(noop);
167             });
169             await waitFor(() =>
170                 expect(result.current.invitees).toEqual([
171                     {
172                         ...inviteeInternal,
173                         email: 'lucienTest.com',
174                         error: new ShareInviteeValidationError(VALIDATION_ERROR_TYPES.INVALID_EMAIL),
175                     },
176                 ])
177             );
178         });
180         it.skip('failed loadInvitee should show error notification', async () => {
181             jest.spyOn(console, 'error').mockImplementationOnce(() => {});
182             const { result, waitFor } = renderHook(() => useShareInvitees([]));
184             const error = new Error('This is an error');
186             act(() => {
187                 result.current.add([inviteeInternal]).catch(noop);
188             });
190             await waitFor(() => expect(result.current.invitees).toEqual([{ ...inviteeInternal, isLoading: true }]));
192             await waitFor(() =>
193                 expect(result.current.invitees).toEqual([{ ...inviteeInternal, isLoading: false, error: error }])
194             );
196             expect(mockedCreateNotification).toHaveBeenCalledWith({
197                 type: 'error',
198                 text: error.message,
199             });
200         });
201     });
203     describe('remove', () => {
204         it('should be able to remove an invitee added previously', async () => {
205             when(mockedGetPrimaryPublicKey)
206                 .calledWith(inviteeInternal2.email, new AbortController().signal)
207                 .mockResolvedValue(primaryPublicKey);
208             const { result } = renderHook(() => useShareInvitees([]));
210             await act(async () => {
211                 result.current.add([inviteeInternal, inviteeInternal2]).catch(noop);
212             });
214             expect(result.current.invitees).toEqual([
215                 { ...inviteeInternal, publicKey, isExternal: false, isLoading: false },
216                 { ...inviteeInternal2, publicKey, isExternal: false, isLoading: false },
217             ]);
219             await act(async () => {
220                 result.current.remove(inviteeInternal.email);
221             });
223             expect(result.current.invitees).toEqual([
224                 { ...inviteeInternal2, publicKey, isExternal: false, isLoading: false },
225             ]);
226         });
227     });
229     describe('clean', () => {
230         it('should be able to clean all invitees added previously and abort requests', async () => {
231             const abortSpy = jest.spyOn(AbortController.prototype, 'abort');
232             const abortController = new AbortController();
233             when(mockedGetPrimaryPublicKey)
234                 .calledWith(inviteeInternal2.email, abortController.signal)
235                 .mockResolvedValue(primaryPublicKey);
236             const { result } = renderHook(() => useShareInvitees([]));
238             await act(async () => {
239                 result.current.add([inviteeInternal, inviteeInternal2]).catch(noop);
240             });
241             expect(result.current.invitees).toEqual([
242                 { ...inviteeInternal, publicKey, isExternal: false, isLoading: false, error: undefined },
243                 { ...inviteeInternal2, publicKey, isExternal: false, isLoading: false, error: undefined },
244             ]);
246             await act(async () => {
247                 result.current.clean();
248             });
250             expect(result.current.invitees).toEqual([]);
251             expect(abortSpy).toHaveBeenCalledTimes(1);
252         });
253     });