Use source loader for email sprite icons
[ProtonMail-WebClient.git] / packages / calendar / calendars / listener.test.ts
blobfb3495e7fb905df514b56aee28e336d77e3de8a8
1 import { combineReducers } from '@reduxjs/toolkit';
3 import { addressKeysReducer } from '@proton/account/addressKeys';
4 import { addressesReducer } from '@proton/account/addresses';
5 import { serverEvent } from '@proton/account/eventLoop';
6 import { getModelState } from '@proton/account/test';
7 import { userReducer } from '@proton/account/user';
8 import { userKeysReducer } from '@proton/account/userKeys';
9 import { getTestStore } from '@proton/redux-shared-store/test';
10 import { CALENDAR_SHARE_BUSY_TIME_SLOTS, CALENDAR_TYPE } from '@proton/shared/lib/calendar/constants';
11 import { EVENT_ACTIONS } from '@proton/shared/lib/constants';
12 import type { UserModel } from '@proton/shared/lib/interfaces';
13 import type {
14     Calendar,
15     CalendarBootstrap,
16     CalendarKey,
17     CalendarMember,
18     CalendarSettings,
19     CalendarWithOwnMembers,
20 } from '@proton/shared/lib/interfaces/calendar';
21 import { CalendarKeyFlags } from '@proton/shared/lib/interfaces/calendar';
23 import { calendarsBootstrapReducer } from '../calendarBootstrap';
24 import { createCalendarModelEventManager } from '../calendarModelEventManager';
25 import { calendarServerEvent } from '../calendarServerEvent';
26 import type { CalendarThunkArguments } from '../interface';
27 import { calendarsReducer } from './index';
28 import { startCalendarEventListener } from './listener';
30 jest.mock('@proton/crypto', () => {
31     return {
32         CryptoProxy: {},
33     };
34 });
35 jest.mock('@proton/srp', () => {});
37 const reducer = combineReducers({
38     ...userReducer,
39     ...userKeysReducer,
40     ...addressesReducer,
41     ...addressKeysReducer,
42     ...calendarsReducer,
43     ...calendarsBootstrapReducer,
44 });
45 const setup = (preloadedState?: Partial<ReturnType<typeof reducer>>) => {
46     const actions: any[] = [];
48     const calendarModelEventManager = createCalendarModelEventManager({
49         api: (async () => {}) as any,
50     });
51     const extraThunkArguments = { calendarModelEventManager } as CalendarThunkArguments;
53     const { store, startListening } = getTestStore({
54         preloadedState: {
55             user: getModelState({ Keys: [{ PrivateKey: '1' }] } as UserModel),
56             calendars: getModelState([]),
57             addresses: getModelState([]),
58             addressKeys: {},
59             calendarsBootstrap: {},
60             ...preloadedState,
61         },
62         reducer,
63         extraThunkArguments,
64     });
66     startCalendarEventListener(startListening);
68     return {
69         store,
70         actions,
71     };
74 const getMockMember = (data: Partial<CalendarMember> & { CalendarID: string }): CalendarMember => {
75     return {
76         ID: 'memberId',
77         Name: '',
78         Description: '',
79         Priority: 1,
80         AddressID: 'addressId',
81         Permissions: 127,
82         Email: 'test@pm.gg',
83         Flags: 1,
84         Color: '#f00',
85         Display: 1,
86         ...data,
87     };
90 const getMockCalendarSettings = (data: Partial<CalendarSettings> & { CalendarID: string }): CalendarSettings => {
91     return {
92         ID: 'id3',
93         DefaultEventDuration: 30,
94         MakesUserBusy: CALENDAR_SHARE_BUSY_TIME_SLOTS.YES,
95         DefaultPartDayNotifications: [
96             {
97                 Type: 1,
98                 Trigger: '-PT17M',
99             },
100             {
101                 Type: 0,
102                 Trigger: '-PT17M',
103             },
104         ],
105         DefaultFullDayNotifications: [
106             {
107                 Type: 1,
108                 Trigger: '-PT17H',
109             },
110             {
111                 Type: 0,
112                 Trigger: '-PT17H',
113             },
114         ],
115         ...data,
116     };
119 const getMockCalendarKey = (data: Partial<CalendarKey> & { CalendarID: string }): CalendarKey => {
120     return {
121         ID: 'key1id',
122         PrivateKey: 'privateKey',
123         PassphraseID: 'passphraseID',
124         Flags: CalendarKeyFlags.ACTIVE,
125         ...data,
126     };
129 const getMockBootstrap = ({ CalendarID }: { CalendarID: string }): CalendarBootstrap => {
130     return {
131         Keys: [getMockCalendarKey({ CalendarID })],
132         Passphrase: {
133             Flags: 1,
134             ID: 'passphraseId',
135             MemberPassphrases: [
136                 {
137                     MemberID: 'memberId',
138                     Passphrase: 'memberPassphrase',
139                     Signature: 'memberSignature',
140                 },
141             ],
142             Invitations: [],
143         },
144         Members: [getMockMember({ CalendarID })],
145         CalendarSettings: getMockCalendarSettings({ CalendarID }),
146     };
149 describe('calendar listener', () => {
150     it('should react to calendar object server events', async () => {
151         const { store } = setup();
153         const getCalendars = () => store.getState().calendars.value;
155         const newCalendar: CalendarWithOwnMembers = {
156             ID: '1',
157             Type: CALENDAR_TYPE.PERSONAL,
158             Owner: {
159                 Email: 'foo@bar.com',
160             },
161             Members: [],
162         };
164         expect(getCalendars()).toEqual([]);
165         store.dispatch(serverEvent({ Calendars: [] }));
166         expect(getCalendars()).toEqual([]);
167         store.dispatch(
168             serverEvent({
169                 Calendars: [{ ID: newCalendar.ID, Action: EVENT_ACTIONS.CREATE, Calendar: newCalendar }],
170             })
171         );
172         expect(getCalendars()).toEqual([newCalendar]);
174         const updatedCalendar: Calendar = {
175             ID: '1',
176             Type: CALENDAR_TYPE.PERSONAL,
177         };
178         store.dispatch(
179             serverEvent({
180                 Calendars: [{ ID: newCalendar.ID, Action: EVENT_ACTIONS.UPDATE, Calendar: updatedCalendar }],
181             })
182         );
183         const newCalendarMember = getMockMember({
184             CalendarID: '1',
185         });
186         expect(getCalendars()).toEqual([newCalendar]);
187         store.dispatch(
188             serverEvent({
189                 CalendarMembers: [
190                     { ID: newCalendarMember.ID, Action: EVENT_ACTIONS.CREATE, Member: newCalendarMember },
191                 ],
192             })
193         );
194         expect(getCalendars()).toEqual([{ ...newCalendar, Members: [newCalendarMember] }]);
196         const unknownCalendarMembers = {
197             ...newCalendarMember,
198             ID: 'unknown-member',
199             CalendarID: 'unknown-calendar',
200         };
201         const oldCalendar = getCalendars();
202         // Should not do anything and keep referential equality
203         store.dispatch(
204             serverEvent({
205                 CalendarMembers: [
206                     {
207                         ID: unknownCalendarMembers.ID,
208                         Action: EVENT_ACTIONS.CREATE,
209                         Member: unknownCalendarMembers,
210                     },
211                 ],
212             })
213         );
214         expect(getCalendars()).toBe(oldCalendar);
215     });
217     it('should remove calendar bootstrap when calendar is deleted', async () => {
218         const initialCalendar = getModelState(getMockBootstrap({ CalendarID: '123' }));
219         const { store } = setup({
220             calendarsBootstrap: {
221                 '123': initialCalendar,
222             },
223         });
224         const getCalendarBootstrap = (calendarID: string) => store.getState().calendarsBootstrap[calendarID]?.value;
226         expect(getCalendarBootstrap('123')).toEqual(initialCalendar.value);
227         store.dispatch(
228             serverEvent({
229                 Calendars: [{ ID: '123', Action: EVENT_ACTIONS.DELETE }],
230             })
231         );
232         expect(getCalendarBootstrap('123')).toEqual(undefined);
233     });
235     it('should update calendar bootstrap settings', async () => {
236         const initialCalendar = getModelState(getMockBootstrap({ CalendarID: '123' }));
237         const { store } = setup({
238             calendarsBootstrap: {
239                 '123': initialCalendar,
240             },
241         });
242         const getCalendarBootstrap = (calendarID: string) => store.getState().calendarsBootstrap[calendarID]?.value;
244         expect(getCalendarBootstrap('123')).toEqual(initialCalendar.value);
245         store.dispatch(
246             calendarServerEvent({
247                 CalendarSettings: [
248                     {
249                         CalendarSettings: getMockCalendarSettings({
250                             CalendarID: '123',
251                             DefaultEventDuration: 1,
252                         }),
253                     },
254                 ],
255             })
256         );
257         expect(getCalendarBootstrap('123')).toEqual({
258             ...initialCalendar.value,
259             CalendarSettings: { ...initialCalendar.value?.CalendarSettings, DefaultEventDuration: 1 },
260         });
261     });
263     it('should remove calendar bootstrap on keys change', async () => {
264         const initialCalendar = getModelState(getMockBootstrap({ CalendarID: '123' }));
265         const initialCalendar2 = getModelState(getMockBootstrap({ CalendarID: '124' }));
266         const { store } = setup({
267             calendarsBootstrap: {
268                 '123': initialCalendar,
269                 '124': initialCalendar2,
270             },
271         });
272         const getCalendarBootstrap = (calendarID: string) => store.getState().calendarsBootstrap[calendarID]?.value;
274         expect(getCalendarBootstrap('123')).toEqual(initialCalendar.value);
275         expect(getCalendarBootstrap('124')).toEqual(initialCalendar2.value);
276         store.dispatch(
277             calendarServerEvent({
278                 CalendarKeys: [
279                     {
280                         ID: '1',
281                         Key: getMockCalendarKey({ CalendarID: '123' }),
282                     },
283                     {
284                         ID: '1',
285                         Key: getMockCalendarKey({ CalendarID: 'unknown' }),
286                     },
287                 ],
288             })
289         );
290         expect(getCalendarBootstrap('123')).toEqual(undefined);
291         expect(getCalendarBootstrap('124')).toEqual(initialCalendar2.value);
293         const calendarKeyWithoutCalendarID = getMockCalendarKey({ CalendarID: '', ID: 'key1id' });
294         store.dispatch(
295             calendarServerEvent({
296                 CalendarKeys: [
297                     {
298                         ID: 'key1id',
299                         Key: calendarKeyWithoutCalendarID,
300                     },
301                 ],
302             })
303         );
304         expect(getCalendarBootstrap('124')).toEqual(undefined);
305     });
307     it('should react to calendar bootstrap object server events', async () => {
308         const initialCalendar = getModelState(getMockBootstrap({ CalendarID: '123' }));
309         const { store } = setup({
310             calendarsBootstrap: {
311                 '123': initialCalendar,
312             },
313         });
315         const getCalendarBootstrap = (calendarID: string) => store.getState().calendarsBootstrap[calendarID]?.value;
317         expect(getCalendarBootstrap('123')).toEqual(initialCalendar.value);
318         store.dispatch(serverEvent({ Calendars: [] }));
319         expect(getCalendarBootstrap('123')).toEqual(initialCalendar.value);
321         const newCalendarMember = getMockMember({ CalendarID: '123', ID: 'new-member' });
322         store.dispatch(
323             serverEvent({
324                 CalendarMembers: [
325                     { ID: newCalendarMember.ID, Action: EVENT_ACTIONS.CREATE, Member: newCalendarMember },
326                 ],
327             })
328         );
330         const newCalendarMembers = [...initialCalendar.value?.Members!, newCalendarMember];
331         expect(getCalendarBootstrap('123')).toEqual({ ...initialCalendar.value, Members: newCalendarMembers });
333         const updatedCalendarMember = getMockMember({ CalendarID: '123', ID: 'new-member', Color: 'red' });
334         store.dispatch(
335             serverEvent({
336                 CalendarMembers: [
337                     { ID: updatedCalendarMember.ID, Action: EVENT_ACTIONS.UPDATE, Member: updatedCalendarMember },
338                 ],
339             })
340         );
342         const updatedCalendarMembers = [...initialCalendar.value?.Members!, updatedCalendarMember];
343         expect(getCalendarBootstrap('123')).toEqual({ ...initialCalendar.value, Members: updatedCalendarMembers });
345         const unknownCalendarMembers = {
346             ...newCalendarMember,
347             ID: 'unknown-member',
348             CalendarID: 'unknown-calendar',
349         };
350         const oldCalendar = getCalendarBootstrap('123');
351         // Should not do anything and keep referential equality
352         store.dispatch(
353             serverEvent({
354                 CalendarMembers: [
355                     {
356                         ID: unknownCalendarMembers.ID,
357                         Action: EVENT_ACTIONS.CREATE,
358                         Member: unknownCalendarMembers,
359                     },
360                 ],
361             })
362         );
363         expect(getCalendarBootstrap('123')).toBe(oldCalendar);
364     });