Remove client-side isLoggedIn value
[ProtonMail-WebClient.git] / packages / shared / test / calendar / alarms.spec.ts
blobcbb426e2ac80b64142241d60686f79af0abda6e5
1 import { differenceInDays, differenceInHours, differenceInMinutes, differenceInWeeks } from 'date-fns';
2 import { enUS } from 'date-fns/locale';
4 import {
5     dedupeAlarmsWithNormalizedTriggers,
6     dedupeNotifications,
7     filterFutureNotifications,
8     getAlarmMessage,
9     sortNotificationsByAscendingTrigger,
10 } from '../../lib/calendar/alarms';
11 import { normalizeTrigger } from '../../lib/calendar/alarms/trigger';
12 import { NOTIFICATION_TYPE_API, NOTIFICATION_UNITS, NOTIFICATION_WHEN } from '../../lib/calendar/constants';
13 import { propertyToUTCDate } from '../../lib/calendar/vcalConverter';
14 import { DAY, HOUR, MINUTE, WEEK } from '../../lib/constants';
15 import { convertUTCDateTimeToZone, convertZonedDateTimeToUTC, toUTCDate } from '../../lib/date/timezone';
16 import { pick } from '../../lib/helpers/object';
17 import type {
18     DateTime,
19     NotificationModel,
20     VcalDateProperty,
21     VcalTriggerProperty,
22     VcalValarmRelativeComponent,
23     VcalVeventComponent,
24 } from '../../lib/interfaces/calendar';
26 const formatOptions = { locale: enUS };
27 const tzidEurope = 'Europe/Zurich';
28 const tzidAsia = 'Asia/Seoul';
30 const getNow = (fakeZonedNow: DateTime, tzid = tzidEurope) => toUTCDate(convertZonedDateTimeToUTC(fakeZonedNow, tzid));
32 describe('getAlarmMessage', () => {
33     const testFakeZonedDate = { year: 2019, month: 10, day: 13, hours: 20, minutes: 0, seconds: 0 };
34     const testComponent = {
35         dtstart: {
36             value: { ...testFakeZonedDate, isUTC: false },
37             parameters: { tzid: tzidEurope },
38         },
39         summary: { value: 'test alarm' },
40     } as VcalVeventComponent;
41     const testFulldayComponent = {
42         dtstart: {
43             value: pick(testFakeZonedDate, ['year', 'month', 'day']),
44             parameters: { type: 'date' },
45         },
46         summary: { value: 'test alarm' },
47     } as VcalVeventComponent;
48     const start = toUTCDate(convertZonedDateTimeToUTC(testFakeZonedDate, tzidEurope));
50     it('treats the part day event as "starts now" when within 30 seconds of the beginning', () => {
51         const commonProps = {
52             component: testComponent,
53             start,
54             tzid: tzidEurope,
55             formatOptions,
56         };
58         expect(
59             getAlarmMessage({
60                 ...commonProps,
61                 now: getNow({ ...testFakeZonedDate, minutes: testFakeZonedDate.minutes - 1, seconds: 29 }),
62             })
63         ).toEqual('test alarm starts at 8:00 PM');
65         expect(
66             getAlarmMessage({
67                 ...commonProps,
68                 now: getNow({ ...testFakeZonedDate, seconds: 31 }),
69             })
70         ).toEqual('test alarm started at 8:00 PM');
72         expect(
73             getAlarmMessage({
74                 ...commonProps,
75                 now: getNow({ ...testFakeZonedDate, minutes: testFakeZonedDate.minutes - 1, seconds: 39 }),
76             })
77         ).toEqual('test alarm starts now');
79         expect(
80             getAlarmMessage({
81                 ...commonProps,
82                 now: getNow({ ...testFakeZonedDate, seconds: 30 }),
83             })
84         ).toEqual('test alarm starts now');
85     });
87     it('it should display the right notification for events happening today', () => {
88         const fakeZonedNow = { ...testFakeZonedDate, hours: 1 };
89         const now = toUTCDate(convertZonedDateTimeToUTC(fakeZonedNow, tzidEurope));
90         expect(getAlarmMessage({ component: testComponent, start, now, tzid: tzidEurope, formatOptions })).toEqual(
91             'test alarm starts at 8:00 PM'
92         );
93     });
95     it('it should display the right notification for full-day events happening today', () => {
96         const fakeZonedNow = { ...testFakeZonedDate, hours: 1 };
97         const now = toUTCDate(convertZonedDateTimeToUTC(fakeZonedNow, tzidEurope));
98         expect(
99             getAlarmMessage({ component: testFulldayComponent, start, now, tzid: tzidEurope, formatOptions })
100         ).toEqual('test alarm starts today');
101     });
103     it('it should display the right notification for events happening tomorrow', () => {
104         const fakeZonedNow = { ...testFakeZonedDate, day: 12 };
105         const now = toUTCDate(convertZonedDateTimeToUTC(fakeZonedNow, tzidEurope));
106         expect(getAlarmMessage({ component: testComponent, start, now, tzid: tzidEurope, formatOptions })).toEqual(
107             'test alarm starts tomorrow at 8:00 PM'
108         );
109     });
111     it('it should display the right notification for full-day events happening tomorrow', () => {
112         const fakeZonedNow = { ...testFakeZonedDate, day: 12 };
113         const now = toUTCDate(convertZonedDateTimeToUTC(fakeZonedNow, tzidEurope));
114         expect(
115             getAlarmMessage({ component: testFulldayComponent, start, now, tzid: tzidEurope, formatOptions })
116         ).toEqual('test alarm starts tomorrow');
117     });
119     it('it should display the right notification for full-day events happening yesterday', () => {
120         const fakeZonedNow = { ...testFakeZonedDate, day: 14 };
121         const now = toUTCDate(convertZonedDateTimeToUTC(fakeZonedNow, tzidEurope));
122         expect(
123             getAlarmMessage({ component: testFulldayComponent, start, now, tzid: tzidEurope, formatOptions })
124         ).toEqual('test alarm started yesterday');
125     });
127     it('it should display the right notification for events happening yesterday', () => {
128         const fakeZonedNow = { ...testFakeZonedDate, day: 14 };
129         const now = toUTCDate(convertZonedDateTimeToUTC(fakeZonedNow, tzidEurope));
130         expect(getAlarmMessage({ component: testComponent, start, now, tzid: tzidEurope, formatOptions })).toEqual(
131             'test alarm started yesterday at 8:00 PM'
132         );
133     });
135     it('it should display the right notification for events happening later this month', () => {
136         const fakeZonedNow = { ...testFakeZonedDate, day: 5 };
137         const now = toUTCDate(convertZonedDateTimeToUTC(fakeZonedNow, tzidEurope));
138         expect(getAlarmMessage({ component: testComponent, start, now, tzid: tzidEurope, formatOptions })).toEqual(
139             'test alarm starts on Sunday 13th at 8:00 PM'
140         );
141     });
143     it('it should display the right notification for full-day events happening later this month', () => {
144         const fakeZonedNow = { ...testFakeZonedDate, day: 5 };
145         const now = toUTCDate(convertZonedDateTimeToUTC(fakeZonedNow, tzidEurope));
146         expect(
147             getAlarmMessage({ component: testFulldayComponent, start, now, tzid: tzidEurope, formatOptions })
148         ).toEqual('test alarm starts on Sunday 13th');
149     });
151     it('it should display the right notification for events happening earlier this month', () => {
152         const fakeZonedNow = { ...testFakeZonedDate, day: 22 };
153         const now = toUTCDate(convertZonedDateTimeToUTC(fakeZonedNow, tzidEurope));
154         expect(getAlarmMessage({ component: testComponent, start, now, tzid: tzidEurope, formatOptions })).toEqual(
155             'test alarm started on Sunday 13th at 8:00 PM'
156         );
157     });
159     it('it should display the right notification for full-day events happening earlier this month', () => {
160         const fakeZonedNow = { ...testFakeZonedDate, day: 22 };
161         const now = toUTCDate(convertZonedDateTimeToUTC(fakeZonedNow, tzidEurope));
162         expect(
163             getAlarmMessage({ component: testFulldayComponent, start, now, tzid: tzidEurope, formatOptions })
164         ).toEqual('test alarm started on Sunday 13th');
165     });
167     it('it should display the right notification for events happening later this year', () => {
168         const fakeZonedNow = { ...testFakeZonedDate, month: 7 };
169         const now = toUTCDate(convertZonedDateTimeToUTC(fakeZonedNow, tzidEurope));
170         expect(getAlarmMessage({ component: testComponent, start, now, tzid: tzidEurope, formatOptions })).toEqual(
171             'test alarm starts on Sunday 13th October at 8:00 PM'
172         );
173     });
175     it('it should display the right notification for full-day events happening later this year', () => {
176         const fakeZonedNow = { ...testFakeZonedDate, month: 7 };
177         const now = toUTCDate(convertZonedDateTimeToUTC(fakeZonedNow, tzidEurope));
178         expect(
179             getAlarmMessage({ component: testFulldayComponent, start, now, tzid: tzidEurope, formatOptions })
180         ).toEqual('test alarm starts on Sunday 13th October');
181     });
183     it('it should display the right notification for events happening earlier this year', () => {
184         const fakeZonedNow = { ...testFakeZonedDate, month: 12 };
185         const now = toUTCDate(convertZonedDateTimeToUTC(fakeZonedNow, tzidEurope));
186         expect(getAlarmMessage({ component: testComponent, start, now, tzid: tzidEurope, formatOptions })).toEqual(
187             'test alarm started on Sunday 13th October at 8:00 PM'
188         );
189     });
191     it('it should display the right notification for full-day events happening earlier this year', () => {
192         const fakeZonedNow = { ...testFakeZonedDate, month: 12 };
193         const now = toUTCDate(convertZonedDateTimeToUTC(fakeZonedNow, tzidEurope));
194         expect(
195             getAlarmMessage({ component: testFulldayComponent, start, now, tzid: tzidEurope, formatOptions })
196         ).toEqual('test alarm started on Sunday 13th October');
197     });
199     it('it should display the right notification for events happening in future years', () => {
200         const fakeZonedNow = { ...testFakeZonedDate, year: 2002 };
201         const now = toUTCDate(convertZonedDateTimeToUTC(fakeZonedNow, tzidEurope));
202         expect(getAlarmMessage({ component: testComponent, start, now, tzid: tzidEurope, formatOptions })).toEqual(
203             'test alarm starts on Sunday, October 13th, 2019 at 8:00 PM'
204         );
205     });
207     it('it should display the right notification for full-day events happening in future years', () => {
208         const fakeZonedNow = { ...testFakeZonedDate, year: 2002 };
209         const now = toUTCDate(convertZonedDateTimeToUTC(fakeZonedNow, tzidEurope));
210         expect(
211             getAlarmMessage({ component: testFulldayComponent, start, now, tzid: tzidEurope, formatOptions })
212         ).toEqual('test alarm starts on Sunday, October 13th, 2019');
213     });
215     it('it should display the right notification for events happening in past years', () => {
216         const fakeZonedNow = { ...testFakeZonedDate, year: 2022 };
217         const now = toUTCDate(convertZonedDateTimeToUTC(fakeZonedNow, tzidEurope));
218         expect(getAlarmMessage({ component: testComponent, start, now, tzid: tzidEurope, formatOptions })).toEqual(
219             'test alarm started on Sunday, October 13th, 2019 at 8:00 PM'
220         );
221     });
223     it('it should display the right notification for full-day events happening in past years', () => {
224         const fakeZonedNow = { ...testFakeZonedDate, year: 2022 };
225         const now = toUTCDate(convertUTCDateTimeToZone(fakeZonedNow, tzidEurope));
226         expect(
227             getAlarmMessage({ component: testFulldayComponent, start, now, tzid: tzidEurope, formatOptions })
228         ).toEqual('test alarm started on Sunday, October 13th, 2019');
229     });
231     it('it should display the right notification for events happening both this month and next year', () => {
232         const fakeZonedNow = { ...testFakeZonedDate, day: 5 };
233         const now = toUTCDate(convertUTCDateTimeToZone(fakeZonedNow, tzidEurope));
234         expect(getAlarmMessage({ component: testComponent, start, now, tzid: tzidEurope, formatOptions })).toEqual(
235             'test alarm starts on Sunday 13th at 8:00 PM'
236         );
237     });
239     it('it should display the right notification for events happening both this month and next year', () => {
240         const fakeZonedNow = { ...testFakeZonedDate, day: 5 };
241         const now = toUTCDate(convertZonedDateTimeToUTC(fakeZonedNow, tzidEurope));
242         expect(getAlarmMessage({ component: testComponent, start, now, tzid: tzidEurope, formatOptions })).toEqual(
243             'test alarm starts on Sunday 13th at 8:00 PM'
244         );
245     });
247     it('it should take into account day changes due to timezone differences', () => {
248         const fakeZonedNow = { ...testFakeZonedDate, hours: 10 };
249         const now = toUTCDate(convertZonedDateTimeToUTC(fakeZonedNow, tzidEurope));
250         expect(getAlarmMessage({ component: testComponent, start, now, tzid: tzidEurope, formatOptions })).toEqual(
251             'test alarm starts at 8:00 PM'
252         );
253         expect(getAlarmMessage({ component: testComponent, start, now, tzid: tzidAsia, formatOptions })).toEqual(
254             'test alarm starts tomorrow at 3:00 AM'
255         );
256     });
259 describe('filterFutureNotifications', () => {
260     it('it should filter future part-day notifications', () => {
261         const isAllDay = false;
262         const atSameTimeNotifications = [
263             {
264                 id: '1',
265                 unit: NOTIFICATION_UNITS.MINUTE,
266                 type: NOTIFICATION_TYPE_API.EMAIL,
267                 when: NOTIFICATION_WHEN.AFTER,
268                 value: 0,
269                 isAllDay,
270             },
271             {
272                 id: '1',
273                 unit: NOTIFICATION_UNITS.DAY,
274                 type: NOTIFICATION_TYPE_API.DEVICE,
275                 when: NOTIFICATION_WHEN.BEFORE,
276                 value: 0,
277                 isAllDay,
278             },
279             {
280                 id: '1',
281                 unit: NOTIFICATION_UNITS.DAY,
282                 type: NOTIFICATION_TYPE_API.EMAIL,
283                 when: NOTIFICATION_WHEN.AFTER,
284                 value: 0,
285                 isAllDay,
286             },
287             {
288                 id: '1',
289                 unit: NOTIFICATION_UNITS.WEEK,
290                 type: NOTIFICATION_TYPE_API.DEVICE,
291                 when: NOTIFICATION_WHEN.BEFORE,
292                 value: 0,
293                 isAllDay,
294             },
295         ];
296         const beforeNotifications = [
297             {
298                 id: '1',
299                 unit: NOTIFICATION_UNITS.MINUTE,
300                 type: NOTIFICATION_TYPE_API.DEVICE,
301                 when: NOTIFICATION_WHEN.BEFORE,
302                 value: 1,
303                 isAllDay,
304             },
305             {
306                 id: '1',
307                 unit: NOTIFICATION_UNITS.DAY,
308                 type: NOTIFICATION_TYPE_API.EMAIL,
309                 when: NOTIFICATION_WHEN.BEFORE,
310                 value: 2,
311                 isAllDay,
312             },
313             {
314                 id: '1',
315                 unit: NOTIFICATION_UNITS.WEEK,
316                 type: NOTIFICATION_TYPE_API.DEVICE,
317                 when: NOTIFICATION_WHEN.BEFORE,
318                 value: 1,
319                 isAllDay,
320             },
321         ];
322         const afterNotifications = [
323             {
324                 id: '1',
325                 unit: NOTIFICATION_UNITS.MINUTE,
326                 type: NOTIFICATION_TYPE_API.DEVICE,
327                 when: NOTIFICATION_WHEN.AFTER,
328                 isAllDay,
329             },
330             {
331                 id: '1',
332                 unit: NOTIFICATION_UNITS.MINUTE,
333                 type: NOTIFICATION_TYPE_API.DEVICE,
334                 when: NOTIFICATION_WHEN.AFTER,
335                 value: 1,
336                 isAllDay,
337             },
338             {
339                 id: '1',
340                 unit: NOTIFICATION_UNITS.DAY,
341                 type: NOTIFICATION_TYPE_API.EMAIL,
342                 when: NOTIFICATION_WHEN.AFTER,
343                 value: 2,
344                 isAllDay,
345             },
346             {
347                 id: '1',
348                 unit: NOTIFICATION_UNITS.WEEK,
349                 type: NOTIFICATION_TYPE_API.DEVICE,
350                 when: NOTIFICATION_WHEN.AFTER,
351                 value: 1,
352                 isAllDay,
353             },
354         ];
355         const notifications = [...beforeNotifications, ...atSameTimeNotifications, ...afterNotifications];
356         const expected = [...beforeNotifications, ...atSameTimeNotifications];
357         expect(filterFutureNotifications(notifications)).toEqual(expected);
358     });
360     it('it should filter future all-day notifications', () => {
361         const isAllDay = true;
362         const onSameDayNotifications = [
363             {
364                 id: '1',
365                 unit: NOTIFICATION_UNITS.DAY,
366                 type: NOTIFICATION_TYPE_API.DEVICE,
367                 when: NOTIFICATION_WHEN.AFTER,
368                 value: 0,
369                 isAllDay,
370             },
371             {
372                 id: '1',
373                 unit: NOTIFICATION_UNITS.DAY,
374                 type: NOTIFICATION_TYPE_API.EMAIL,
375                 when: NOTIFICATION_WHEN.BEFORE,
376                 value: 0,
377                 isAllDay,
378             },
379             {
380                 id: '1',
381                 unit: NOTIFICATION_UNITS.WEEK,
382                 type: NOTIFICATION_TYPE_API.EMAIL,
383                 when: NOTIFICATION_WHEN.AFTER,
384                 value: 0,
385                 isAllDay,
386             },
387             {
388                 id: '1',
389                 unit: NOTIFICATION_UNITS.WEEK,
390                 type: NOTIFICATION_TYPE_API.DEVICE,
391                 when: NOTIFICATION_WHEN.BEFORE,
392                 value: 0,
393                 isAllDay,
394             },
395         ];
396         const beforeNotifications = [
397             {
398                 id: '1',
399                 unit: NOTIFICATION_UNITS.DAY,
400                 type: NOTIFICATION_TYPE_API.DEVICE,
401                 when: NOTIFICATION_WHEN.BEFORE,
402                 value: 1,
403                 isAllDay,
404             },
405             {
406                 id: '1',
407                 unit: NOTIFICATION_UNITS.DAY,
408                 type: NOTIFICATION_TYPE_API.EMAIL,
409                 when: NOTIFICATION_WHEN.BEFORE,
410                 value: 2,
411                 isAllDay,
412             },
413             {
414                 id: '1',
415                 unit: NOTIFICATION_UNITS.WEEK,
416                 type: NOTIFICATION_TYPE_API.DEVICE,
417                 when: NOTIFICATION_WHEN.BEFORE,
418                 value: 1,
419                 isAllDay,
420             },
421         ];
422         const afterNotifications = [
423             {
424                 id: '1',
425                 unit: NOTIFICATION_UNITS.DAY,
426                 type: NOTIFICATION_TYPE_API.DEVICE,
427                 when: NOTIFICATION_WHEN.AFTER,
428                 isAllDay,
429             },
430             {
431                 id: '1',
432                 unit: NOTIFICATION_UNITS.DAY,
433                 type: NOTIFICATION_TYPE_API.DEVICE,
434                 when: NOTIFICATION_WHEN.AFTER,
435                 value: 1,
436                 isAllDay,
437             },
438             {
439                 id: '1',
440                 unit: NOTIFICATION_UNITS.WEEK,
441                 type: NOTIFICATION_TYPE_API.EMAIL,
442                 when: NOTIFICATION_WHEN.AFTER,
443                 value: 2,
444                 isAllDay,
445             },
446             {
447                 id: '1',
448                 unit: NOTIFICATION_UNITS.WEEK,
449                 type: NOTIFICATION_TYPE_API.DEVICE,
450                 when: NOTIFICATION_WHEN.AFTER,
451                 value: 1,
452                 isAllDay,
453             },
454         ];
455         const notifications = [...beforeNotifications, ...onSameDayNotifications, ...afterNotifications];
456         const expected = [...beforeNotifications, ...onSameDayNotifications];
457         expect(filterFutureNotifications(notifications)).toEqual(expected);
458     });
461 describe('normalizeTrigger', () => {
462     const dtstartPartDay = {
463         value: { year: 2020, month: 5, day: 11, hours: 12, minutes: 30, seconds: 0, isUTC: true },
464     };
465     const dtstartAllDay = {
466         value: { year: 2020, month: 5, day: 11 },
467         parameters: { type: 'date' },
468     } as VcalDateProperty;
469     const utcStartPartDay = propertyToUTCDate(dtstartPartDay);
471     it('should keep just one component for part-day events with relative triggers', () => {
472         const triggerValues = [
473             { weeks: 1, days: 6, hours: 0, minutes: 30, seconds: 0, isNegative: true },
474             { weeks: 0, days: 1, hours: 2, minutes: 1, seconds: 30, isNegative: false },
475             { weeks: 1, days: 3, hours: 2, minutes: 0, seconds: 0, isNegative: true },
476             { weeks: 2, days: 0, hours: 0, minutes: 0, seconds: 30, isNegative: false },
477             { weeks: 2, days: 0, hours: 0, minutes: 0, seconds: 0, isNegative: true },
478             { weeks: 2, days: 7, hours: 7 * 24, minutes: 0, seconds: 0, isNegative: true },
479         ];
480         const expected = [
481             {
482                 weeks: 0,
483                 days: 0,
484                 hours: 0,
485                 minutes: (WEEK + 6 * DAY + 30 * MINUTE) / MINUTE,
486                 seconds: 0,
487                 isNegative: true,
488             },
489             { weeks: 0, days: 0, hours: 0, minutes: (DAY + 2 * HOUR + MINUTE) / MINUTE, seconds: 0, isNegative: false },
490             { weeks: 0, days: 0, hours: (WEEK + 3 * DAY + 2 * HOUR) / HOUR, minutes: 0, seconds: 0, isNegative: true },
491             { weeks: 2, days: 0, hours: 0, minutes: 0, seconds: 0, isNegative: false },
492             { weeks: 2, days: 0, hours: 0, minutes: 0, seconds: 0, isNegative: true },
493             { weeks: 4, days: 0, hours: 0, minutes: 0, seconds: 0, isNegative: true },
494         ];
495         const results = triggerValues.map((trigger) => normalizeTrigger({ value: trigger }, dtstartPartDay));
496         expect(results).toEqual(expected);
497     });
499     it('should keep just one component for part-day events with absolute triggers', () => {
500         const triggerValues = [
501             { year: 2020, month: 5, day: 2, hours: 9, minutes: 0, seconds: 0, isUTC: true },
502             { year: 2020, month: 4, day: 12, hours: 9, minutes: 30, seconds: 0, isUTC: true },
503             { year: 2020, month: 5, day: 22, hours: 12, minutes: 30, seconds: 0, isUTC: true },
504             { year: 2020, month: 8, day: 1, hours: 0, minutes: 15, seconds: 0, isUTC: true },
505             { year: 2000, month: 5, day: 15, hours: 12, minutes: 30, seconds: 0, isUTC: true },
506         ];
507         const utcDates = triggerValues.map((dateTime) => propertyToUTCDate({ value: dateTime }));
508         const expected = [
509             {
510                 weeks: 0,
511                 days: 0,
512                 hours: 0,
513                 minutes: differenceInMinutes(utcStartPartDay, utcDates[0]),
514                 seconds: 0,
515                 isNegative: true,
516             },
517             {
518                 weeks: 0,
519                 days: 0,
520                 hours: differenceInHours(utcStartPartDay, utcDates[1]),
521                 minutes: 0,
522                 seconds: 0,
523                 isNegative: true,
524             },
525             {
526                 weeks: 0,
527                 days: -differenceInDays(utcStartPartDay, utcDates[2]),
528                 hours: 0,
529                 minutes: 0,
530                 seconds: 0,
531                 isNegative: false,
532             },
533             {
534                 weeks: 0,
535                 days: 0,
536                 hours: 0,
537                 minutes: -differenceInMinutes(utcStartPartDay, utcDates[3]),
538                 seconds: 0,
539                 isNegative: false,
540             },
541             {
542                 weeks: differenceInWeeks(utcStartPartDay, utcDates[4]),
543                 days: 0,
544                 hours: 0,
545                 minutes: 0,
546                 seconds: 0,
547                 isNegative: true,
548             },
549         ];
550         const results = triggerValues.map((triggerValue) => {
551             const trigger = {
552                 value: triggerValue,
553                 parameters: { type: 'date-time' },
554             } as VcalTriggerProperty;
555             return normalizeTrigger(trigger, dtstartPartDay);
556         });
557         expect(results).toEqual(expected);
558     });
560     it('should keep all components for all-day events (except forbidden combinations of weeks and days) for relative triggers', () => {
561         const triggerValues = [
562             { weeks: 1, days: 6, hours: 0, minutes: 30, seconds: 0, isNegative: true },
563             { weeks: 0, days: 1, hours: 2, minutes: 1, seconds: 30, isNegative: false },
564             { weeks: 1, days: 3, hours: 2, minutes: 0, seconds: 0, isNegative: true },
565             { weeks: 2, days: 0, hours: 0, minutes: 0, seconds: 30, isNegative: false },
566             { weeks: 2, days: 0, hours: 0, minutes: 0, seconds: 0, isNegative: true },
567         ];
568         const expected = [
569             { weeks: 1, days: 6, hours: 0, minutes: 30, seconds: 0, isNegative: true },
570             { weeks: 0, days: 1, hours: 2, minutes: 1, seconds: 0, isNegative: false },
571             { weeks: 0, days: 10, hours: 2, minutes: 0, seconds: 0, isNegative: true },
572             { weeks: 2, days: 0, hours: 0, minutes: 0, seconds: 0, isNegative: false },
573             { weeks: 2, days: 0, hours: 0, minutes: 0, seconds: 0, isNegative: true },
574         ];
575         const results = triggerValues.map((trigger) => normalizeTrigger({ value: trigger }, dtstartAllDay));
576         expect(results).toEqual(expected);
577     });
579     it('should keep all components for all-day events (except forbidden combinations of weeks and days) for absolute  triggers', () => {
580         const triggerValues = [
581             { year: 2020, month: 5, day: 2, hours: 9, minutes: 0, seconds: 0, isUTC: true },
582             { year: 2020, month: 4, day: 12, hours: 9, minutes: 30, seconds: 0, isUTC: true },
583             { year: 2020, month: 5, day: 22, hours: 12, minutes: 30, seconds: 0, isUTC: true },
584             { year: 2020, month: 8, day: 1, hours: 0, minutes: 15, seconds: 0, isUTC: true },
585             { year: 2000, month: 5, day: 15, hours: 12, minutes: 30, seconds: 0, isUTC: true },
586         ];
587         const expected = [
588             { weeks: 0, days: 8, hours: 15, minutes: 0, seconds: 0, isNegative: true },
589             { weeks: 0, days: 28, hours: 14, minutes: 30, seconds: 0, isNegative: true },
590             { weeks: 0, days: 11, hours: 12, minutes: 30, seconds: 0, isNegative: false },
591             { weeks: 0, days: 82, hours: 0, minutes: 15, seconds: 0, isNegative: false },
592             { weeks: 1042, days: 6, hours: 11, minutes: 30, seconds: 0, isNegative: true },
593         ];
594         const results = triggerValues.map((triggerValue) => {
595             const trigger = {
596                 value: triggerValue,
597                 parameters: { type: 'date-time' },
598             } as VcalTriggerProperty;
599             return normalizeTrigger(trigger, dtstartAllDay);
600         });
601         expect(results).toEqual(expected);
602     });
605 describe('dedupeNotifications', () => {
606     it('de-duplicates alarms as expected', () => {
607         const notifications = [
608             {
609                 id: 'one',
610                 unit: NOTIFICATION_UNITS.WEEK,
611                 type: NOTIFICATION_TYPE_API.DEVICE,
612                 when: NOTIFICATION_WHEN.BEFORE,
613                 value: 1,
614                 isAllDay: false,
615             },
616             {
617                 id: 'two',
618                 unit: NOTIFICATION_UNITS.DAY,
619                 type: NOTIFICATION_TYPE_API.DEVICE,
620                 when: NOTIFICATION_WHEN.BEFORE,
621                 value: 7,
622                 isAllDay: false,
623             },
624             {
625                 id: 'three',
626                 unit: NOTIFICATION_UNITS.HOUR,
627                 type: NOTIFICATION_TYPE_API.DEVICE,
628                 when: NOTIFICATION_WHEN.BEFORE,
629                 value: 168,
630                 isAllDay: false,
631             },
632             {
633                 id: 'four',
634                 unit: NOTIFICATION_UNITS.WEEK,
635                 type: NOTIFICATION_TYPE_API.DEVICE,
636                 when: NOTIFICATION_WHEN.BEFORE,
637                 value: 1,
638                 isAllDay: true,
639                 at: new Date(2000, 1, 1),
640             },
641             {
642                 id: 'five',
643                 unit: NOTIFICATION_UNITS.DAY,
644                 type: NOTIFICATION_TYPE_API.DEVICE,
645                 when: NOTIFICATION_WHEN.BEFORE,
646                 value: 7,
647                 isAllDay: true,
648                 at: new Date(2000, 1, 1),
649             },
650             {
651                 id: 'six',
652                 unit: NOTIFICATION_UNITS.HOUR,
653                 type: NOTIFICATION_TYPE_API.DEVICE,
654                 when: NOTIFICATION_WHEN.BEFORE,
655                 value: 168,
656                 isAllDay: true,
657                 at: new Date(2000, 1, 1),
658             },
659         ];
661         expect(dedupeNotifications(notifications)).toEqual([
662             {
663                 id: 'one',
664                 unit: NOTIFICATION_UNITS.WEEK,
665                 type: NOTIFICATION_TYPE_API.DEVICE,
666                 when: NOTIFICATION_WHEN.BEFORE,
667                 value: 1,
668                 isAllDay: false,
669             },
670             {
671                 id: 'six',
672                 at: new Date(2000, 1, 1),
673                 isAllDay: true,
674                 type: NOTIFICATION_TYPE_API.DEVICE,
675                 unit: NOTIFICATION_UNITS.HOUR,
676                 value: 168,
677                 when: NOTIFICATION_WHEN.BEFORE,
678             },
679         ]);
680     });
682     it('sorts when deduping', () => {
683         const notifications = [
684             {
685                 id: '14-days',
686                 unit: NOTIFICATION_UNITS.DAY,
687                 type: NOTIFICATION_TYPE_API.DEVICE,
688                 when: NOTIFICATION_WHEN.BEFORE,
689                 value: 14,
690                 isAllDay: false,
691             },
692             {
693                 id: '24-hours',
694                 unit: NOTIFICATION_UNITS.HOUR,
695                 type: NOTIFICATION_TYPE_API.DEVICE,
696                 when: NOTIFICATION_WHEN.BEFORE,
697                 value: 24,
698                 isAllDay: false,
699             },
700             {
701                 id: 'two-weeks',
702                 unit: NOTIFICATION_UNITS.WEEK,
703                 type: NOTIFICATION_TYPE_API.DEVICE,
704                 when: NOTIFICATION_WHEN.BEFORE,
705                 value: 2,
706                 isAllDay: false,
707             },
708             {
709                 id: '1-day',
710                 unit: NOTIFICATION_UNITS.DAY,
711                 type: NOTIFICATION_TYPE_API.DEVICE,
712                 when: NOTIFICATION_WHEN.BEFORE,
713                 value: 1,
714                 isAllDay: false,
715             },
716         ];
718         expect(dedupeNotifications(notifications)).toEqual([
719             {
720                 id: '1-day',
721                 unit: NOTIFICATION_UNITS.DAY,
722                 type: NOTIFICATION_TYPE_API.DEVICE,
723                 when: NOTIFICATION_WHEN.BEFORE,
724                 value: 1,
725                 isAllDay: false,
726             },
727             {
728                 id: 'two-weeks',
729                 unit: NOTIFICATION_UNITS.WEEK,
730                 type: NOTIFICATION_TYPE_API.DEVICE,
731                 when: NOTIFICATION_WHEN.BEFORE,
732                 value: 2,
733                 isAllDay: false,
734             },
735         ]);
736     });
739 describe('dedupeAlarmsWithNormalizedTriggers()', () => {
740     it('dedupes alarms', () => {
741         const alarms: VcalValarmRelativeComponent[] = [
742             {
743                 component: 'valarm',
744                 action: {
745                     value: 'DISPLAY',
746                 },
747                 trigger: {
748                     value: {
749                         weeks: 0,
750                         days: 0,
751                         hours: 24,
752                         minutes: 0,
753                         seconds: 0,
754                         isNegative: true,
755                     },
756                 },
757             },
758             {
759                 component: 'valarm',
760                 action: {
761                     value: 'DISPLAY',
762                 },
763                 trigger: {
764                     value: {
765                         weeks: 0,
766                         days: 1,
767                         hours: 0,
768                         minutes: 0,
769                         seconds: 0,
770                         isNegative: false,
771                     },
772                 },
773             },
774             {
775                 component: 'valarm',
776                 action: {
777                     value: 'DISPLAY',
778                 },
779                 trigger: {
780                     value: {
781                         weeks: 0,
782                         days: 0,
783                         hours: 0,
784                         minutes: 20160, // 2 weeks
785                         seconds: 0,
786                         isNegative: true,
787                     },
788                 },
789             },
790             {
791                 component: 'valarm',
792                 action: {
793                     value: 'DISPLAY',
794                 },
795                 trigger: {
796                     value: {
797                         weeks: 0,
798                         days: 14, // 2 weeks
799                         hours: 0,
800                         minutes: 0,
801                         seconds: 0,
802                         isNegative: true,
803                     },
804                 },
805             },
806             {
807                 component: 'valarm',
808                 action: {
809                     value: 'DISPLAY',
810                 },
811                 trigger: {
812                     value: {
813                         weeks: 0,
814                         days: 0,
815                         hours: 337,
816                         minutes: 0,
817                         seconds: 0,
818                         isNegative: true,
819                     },
820                 },
821             },
822             {
823                 component: 'valarm',
824                 action: {
825                     value: 'DISPLAY',
826                 },
827                 trigger: {
828                     value: {
829                         weeks: 2,
830                         days: 0,
831                         hours: 0,
832                         minutes: 0,
833                         seconds: 0,
834                         isNegative: true,
835                     },
836                 },
837             },
838             {
839                 component: 'valarm',
840                 action: {
841                     value: 'DISPLAY',
842                 },
843                 trigger: {
844                     value: {
845                         weeks: 0,
846                         days: 1,
847                         hours: 0,
848                         minutes: 0,
849                         seconds: 0,
850                         isNegative: true,
851                     },
852                 },
853             },
854         ];
855         const expectedAlarms: VcalValarmRelativeComponent[] = [
856             {
857                 component: 'valarm',
858                 action: {
859                     value: 'DISPLAY',
860                 },
861                 trigger: {
862                     value: {
863                         weeks: 0,
864                         days: 1,
865                         hours: 0,
866                         minutes: 0,
867                         seconds: 0,
868                         isNegative: false,
869                     },
870                 },
871             },
872             {
873                 component: 'valarm',
874                 action: {
875                     value: 'DISPLAY',
876                 },
877                 trigger: {
878                     value: {
879                         weeks: 0,
880                         days: 1,
881                         hours: 0,
882                         minutes: 0,
883                         seconds: 0,
884                         isNegative: true,
885                     },
886                 },
887             },
888             {
889                 component: 'valarm',
890                 action: {
891                     value: 'DISPLAY',
892                 },
893                 trigger: {
894                     value: {
895                         weeks: 2,
896                         days: 0,
897                         hours: 0,
898                         minutes: 0,
899                         seconds: 0,
900                         isNegative: true,
901                     },
902                 },
903             },
904             {
905                 component: 'valarm',
906                 action: {
907                     value: 'DISPLAY',
908                 },
909                 trigger: {
910                     value: {
911                         weeks: 0,
912                         days: 0,
913                         hours: 337,
914                         minutes: 0,
915                         seconds: 0,
916                         isNegative: true,
917                     },
918                 },
919             },
920         ];
921         expect(dedupeAlarmsWithNormalizedTriggers(alarms)).toEqual(expectedAlarms);
922     });
925 describe('sortNotificationsByAscendingTrigger()', () => {
926     it('sorts case 1 correctly', () => {
927         const a = {
928             unit: NOTIFICATION_UNITS.MINUTE,
929             type: NOTIFICATION_TYPE_API.DEVICE,
930             when: NOTIFICATION_WHEN.AFTER,
931             value: 0,
932             isAllDay: false,
933         };
934         const b = {
935             unit: NOTIFICATION_UNITS.HOUR,
936             type: NOTIFICATION_TYPE_API.DEVICE,
937             when: NOTIFICATION_WHEN.BEFORE,
938             value: 1,
939             isAllDay: false,
940         };
941         const c = {
942             unit: NOTIFICATION_UNITS.HOUR,
943             type: NOTIFICATION_TYPE_API.DEVICE,
944             when: NOTIFICATION_WHEN.BEFORE,
945             value: 1,
946             isAllDay: false,
947         };
948         const d = {
949             unit: NOTIFICATION_UNITS.MINUTE,
950             type: NOTIFICATION_TYPE_API.DEVICE,
951             when: NOTIFICATION_WHEN.BEFORE,
952             value: 60,
953             isAllDay: false,
954         };
955         const input = [a, b, c, d] as NotificationModel[];
956         const expectedResult = [b, c, d, a] as NotificationModel[];
957         expect(sortNotificationsByAscendingTrigger(input)).toEqual(expectedResult);
958     });
960     it('sorts case 2 correctly', () => {
961         const a = {
962             unit: NOTIFICATION_UNITS.DAY,
963             type: NOTIFICATION_TYPE_API.DEVICE,
964             when: NOTIFICATION_WHEN.AFTER,
965             at: new Date(2000, 0, 1, 12, 30),
966             value: 1,
967             isAllDay: true,
968         };
970         const b = {
971             unit: NOTIFICATION_UNITS.DAY,
972             type: NOTIFICATION_TYPE_API.DEVICE,
973             when: NOTIFICATION_WHEN.BEFORE,
974             at: new Date(2000, 0, 1, 1, 30),
975             value: 1,
976             isAllDay: true,
977         };
979         const c = {
980             unit: NOTIFICATION_UNITS.DAY,
981             type: NOTIFICATION_TYPE_API.DEVICE,
982             when: NOTIFICATION_WHEN.BEFORE,
983             at: new Date(2000, 0, 1, 9, 30),
984             value: 1,
985             isAllDay: true,
986         };
988         const d = {
989             unit: NOTIFICATION_UNITS.DAY,
990             type: NOTIFICATION_TYPE_API.DEVICE,
991             when: NOTIFICATION_WHEN.BEFORE,
992             at: new Date(2000, 0, 1, 9, 30),
993             value: 7,
994             isAllDay: true,
995         };
997         const e = {
998             unit: NOTIFICATION_UNITS.WEEK,
999             type: NOTIFICATION_TYPE_API.DEVICE,
1000             when: NOTIFICATION_WHEN.BEFORE,
1001             at: new Date(2000, 0, 1, 9, 30),
1002             value: 1,
1003             isAllDay: true,
1004         };
1006         const input = [a, b, c, d, e] as NotificationModel[];
1007         const expectedResult = [d, e, b, c, a] as NotificationModel[];
1009         expect(sortNotificationsByAscendingTrigger(input)).toEqual(expectedResult);
1010     });