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 = {
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))),
44 jest.mocked(useNotifications).mockReturnValue({
45 createNotification: mockedCreateNotification,
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(),
58 jest.mock('../../../../store/_shares/useDriveSharingFlags', () => ({
59 useDriveSharingFlags: jest
61 .mockReturnValue({ isSharingExternalInviteDisabled: false, isSharingExternalInviteAvailable: true }),
64 const primaryPublicKey = 'primaryPublicKey';
67 _keyContentHash: ['dashdhuiwy323213dsahjeg123g21312', '123heiuwqhdqighdy1t223813y2weaseguy'],
69 } as PublicKeyReference;
71 describe('useShareInvitees', () => {
73 when(mockedGetPrimaryPublicKey)
74 .calledWith(inviteeInternal.email, new AbortController().signal)
75 .mockResolvedValue(primaryPublicKey);
77 when(mockedImportPublicKey).calledWith({ armoredKey: primaryPublicKey }).mockResolvedValue(publicKey);
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 () => {
92 .add([inviteeInternal, inviteeInternal2, inviteeExternalProton, inviteeExternalNonProton])
96 expect(result.all[1].invitees).toEqual([
97 { ...inviteeInternal, isLoading: true },
98 { ...inviteeInternal2, isLoading: true },
99 { ...inviteeExternalProton, isLoading: true },
100 { ...inviteeExternalNonProton, isLoading: true },
103 expect(result.current.invitees).toEqual([
104 { ...inviteeInternal, publicKey, isExternal: false, isLoading: false },
112 ...inviteeExternalProton,
118 ...inviteeExternalNonProton,
124 verifyAllWhenMocksCalled();
127 it('should show duplicate notification', async () => {
128 const { result, waitFor } = renderHook(() => useShareInvitees([]));
131 result.current.add([inviteeInternal, inviteeInternal]).catch(noop);
135 expect(result.current.invitees).toEqual([
136 { ...inviteeInternal, publicKey, isExternal: false, isLoading: false, error: undefined },
139 expect(mockedCreateNotification).toHaveBeenCalledWith({
141 text: `Removed duplicate invitees: ${inviteeInternal.email}`,
145 it('adding an existing invitee should add it with an error', async () => {
146 const { result, waitFor } = renderHook(() => useShareInvitees([inviteeInternal.email]));
149 result.current.add([inviteeInternal]).catch(noop);
153 expect(result.current.invitees).toEqual([
156 error: new ShareInviteeValidationError(VALIDATION_ERROR_TYPES.EXISTING_MEMBER),
162 it('adding an invitee with bad email should add it with an error', async () => {
163 const { result, waitFor } = renderHook(() => useShareInvitees([]));
166 result.current.add([{ ...inviteeInternal, email: 'lucienTest.com' }]).catch(noop);
170 expect(result.current.invitees).toEqual([
173 email: 'lucienTest.com',
174 error: new ShareInviteeValidationError(VALIDATION_ERROR_TYPES.INVALID_EMAIL),
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');
187 result.current.add([inviteeInternal]).catch(noop);
190 await waitFor(() => expect(result.current.invitees).toEqual([{ ...inviteeInternal, isLoading: true }]));
193 expect(result.current.invitees).toEqual([{ ...inviteeInternal, isLoading: false, error: error }])
196 expect(mockedCreateNotification).toHaveBeenCalledWith({
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);
214 expect(result.current.invitees).toEqual([
215 { ...inviteeInternal, publicKey, isExternal: false, isLoading: false },
216 { ...inviteeInternal2, publicKey, isExternal: false, isLoading: false },
219 await act(async () => {
220 result.current.remove(inviteeInternal.email);
223 expect(result.current.invitees).toEqual([
224 { ...inviteeInternal2, publicKey, isExternal: false, isLoading: false },
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);
241 expect(result.current.invitees).toEqual([
242 { ...inviteeInternal, publicKey, isExternal: false, isLoading: false, error: undefined },
243 { ...inviteeInternal2, publicKey, isExternal: false, isLoading: false, error: undefined },
246 await act(async () => {
247 result.current.clean();
250 expect(result.current.invitees).toEqual([]);
251 expect(abortSpy).toHaveBeenCalledTimes(1);