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';
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', () => {
35 jest.mock('@proton/srp', () => {});
37 const reducer = combineReducers({
41 ...addressKeysReducer,
43 ...calendarsBootstrapReducer,
45 const setup = (preloadedState?: Partial<ReturnType<typeof reducer>>) => {
46 const actions: any[] = [];
48 const calendarModelEventManager = createCalendarModelEventManager({
49 api: (async () => {}) as any,
51 const extraThunkArguments = { calendarModelEventManager } as CalendarThunkArguments;
53 const { store, startListening } = getTestStore({
55 user: getModelState({ Keys: [{ PrivateKey: '1' }] } as UserModel),
56 calendars: getModelState([]),
57 addresses: getModelState([]),
59 calendarsBootstrap: {},
66 startCalendarEventListener(startListening);
74 const getMockMember = (data: Partial<CalendarMember> & { CalendarID: string }): CalendarMember => {
80 AddressID: 'addressId',
90 const getMockCalendarSettings = (data: Partial<CalendarSettings> & { CalendarID: string }): CalendarSettings => {
93 DefaultEventDuration: 30,
94 MakesUserBusy: CALENDAR_SHARE_BUSY_TIME_SLOTS.YES,
95 DefaultPartDayNotifications: [
105 DefaultFullDayNotifications: [
119 const getMockCalendarKey = (data: Partial<CalendarKey> & { CalendarID: string }): CalendarKey => {
122 PrivateKey: 'privateKey',
123 PassphraseID: 'passphraseID',
124 Flags: CalendarKeyFlags.ACTIVE,
129 const getMockBootstrap = ({ CalendarID }: { CalendarID: string }): CalendarBootstrap => {
131 Keys: [getMockCalendarKey({ CalendarID })],
137 MemberID: 'memberId',
138 Passphrase: 'memberPassphrase',
139 Signature: 'memberSignature',
144 Members: [getMockMember({ CalendarID })],
145 CalendarSettings: getMockCalendarSettings({ CalendarID }),
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 = {
157 Type: CALENDAR_TYPE.PERSONAL,
159 Email: 'foo@bar.com',
164 expect(getCalendars()).toEqual([]);
165 store.dispatch(serverEvent({ Calendars: [] }));
166 expect(getCalendars()).toEqual([]);
169 Calendars: [{ ID: newCalendar.ID, Action: EVENT_ACTIONS.CREATE, Calendar: newCalendar }],
172 expect(getCalendars()).toEqual([newCalendar]);
174 const updatedCalendar: Calendar = {
176 Type: CALENDAR_TYPE.PERSONAL,
180 Calendars: [{ ID: newCalendar.ID, Action: EVENT_ACTIONS.UPDATE, Calendar: updatedCalendar }],
183 const newCalendarMember = getMockMember({
186 expect(getCalendars()).toEqual([newCalendar]);
190 { ID: newCalendarMember.ID, Action: EVENT_ACTIONS.CREATE, Member: newCalendarMember },
194 expect(getCalendars()).toEqual([{ ...newCalendar, Members: [newCalendarMember] }]);
196 const unknownCalendarMembers = {
197 ...newCalendarMember,
198 ID: 'unknown-member',
199 CalendarID: 'unknown-calendar',
201 const oldCalendar = getCalendars();
202 // Should not do anything and keep referential equality
207 ID: unknownCalendarMembers.ID,
208 Action: EVENT_ACTIONS.CREATE,
209 Member: unknownCalendarMembers,
214 expect(getCalendars()).toBe(oldCalendar);
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,
224 const getCalendarBootstrap = (calendarID: string) => store.getState().calendarsBootstrap[calendarID]?.value;
226 expect(getCalendarBootstrap('123')).toEqual(initialCalendar.value);
229 Calendars: [{ ID: '123', Action: EVENT_ACTIONS.DELETE }],
232 expect(getCalendarBootstrap('123')).toEqual(undefined);
235 it('should update calendar bootstrap settings', async () => {
236 const initialCalendar = getModelState(getMockBootstrap({ CalendarID: '123' }));
237 const { store } = setup({
238 calendarsBootstrap: {
239 '123': initialCalendar,
242 const getCalendarBootstrap = (calendarID: string) => store.getState().calendarsBootstrap[calendarID]?.value;
244 expect(getCalendarBootstrap('123')).toEqual(initialCalendar.value);
246 calendarServerEvent({
249 CalendarSettings: getMockCalendarSettings({
251 DefaultEventDuration: 1,
257 expect(getCalendarBootstrap('123')).toEqual({
258 ...initialCalendar.value,
259 CalendarSettings: { ...initialCalendar.value?.CalendarSettings, DefaultEventDuration: 1 },
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,
272 const getCalendarBootstrap = (calendarID: string) => store.getState().calendarsBootstrap[calendarID]?.value;
274 expect(getCalendarBootstrap('123')).toEqual(initialCalendar.value);
275 expect(getCalendarBootstrap('124')).toEqual(initialCalendar2.value);
277 calendarServerEvent({
281 Key: getMockCalendarKey({ CalendarID: '123' }),
285 Key: getMockCalendarKey({ CalendarID: 'unknown' }),
290 expect(getCalendarBootstrap('123')).toEqual(undefined);
291 expect(getCalendarBootstrap('124')).toEqual(initialCalendar2.value);
293 const calendarKeyWithoutCalendarID = getMockCalendarKey({ CalendarID: '', ID: 'key1id' });
295 calendarServerEvent({
299 Key: calendarKeyWithoutCalendarID,
304 expect(getCalendarBootstrap('124')).toEqual(undefined);
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,
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' });
325 { ID: newCalendarMember.ID, Action: EVENT_ACTIONS.CREATE, Member: newCalendarMember },
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' });
337 { ID: updatedCalendarMember.ID, Action: EVENT_ACTIONS.UPDATE, Member: updatedCalendarMember },
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',
350 const oldCalendar = getCalendarBootstrap('123');
351 // Should not do anything and keep referential equality
356 ID: unknownCalendarMembers.ID,
357 Action: EVENT_ACTIONS.CREATE,
358 Member: unknownCalendarMembers,
363 expect(getCalendarBootstrap('123')).toBe(oldCalendar);