3 getMillisecondsFromTriggerString,
4 getVeventWithoutErrors,
6 parseVcalendarWithRecoveryAndMaybeErrors,
8 } from '../../lib/calendar/vcal';
9 import { DAY, HOUR, MINUTE, SECOND, WEEK } from '../../lib/constants';
13 VcalVcalendarWithMaybeErrors,
15 VcalVeventComponentWithMaybeErrors,
16 } from '../../lib/interfaces/calendar';
18 const vevent = `BEGIN:VEVENT
19 DTSTAMP:20190719T130854Z
20 UID:7E018059-2165-4170-B32F-6936E88E61E5
21 DTSTART;TZID=America/New_York:20190719T120000
22 DTEND;TZID=Europe/Zurich:20190719T130000
24 CATEGORIES:ANNIVERSARY,PERSONAL,SPECIAL OCCASION
25 SUMMARY:Our Blissful Anniversary
28 const allDayVevent = `BEGIN:VEVENT
29 UID:9E018059-2165-4170-B32F-6936E88E61E5
30 DTSTART;VALUE=DATE:20190812
31 DTEND;VALUE=DATE:20190813
35 const veventWithRecurrenceId = `BEGIN:VEVENT
36 UID:9E018059-2165-4170-B32F-6936E88E61E5
37 RECURRENCE-ID;TZID=Europe/Zurich:20200311T100000
38 DTSTART;TZID=Europe/Zurich:20200311T100000
39 DTEND;TZID=Europe/Zurich:20200312T100000
43 const veventWithAttendees = `BEGIN:VEVENT
44 UID:7E018059-2165-4170-B32F-6936E88E61E5
45 DTSTART;VALUE=DATE:20190812
46 DTEND;VALUE=DATE:20190813
48 ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;RSVP=TRUE;X-PM-TOKEN=123;CN
49 =james@bond.co.uk:mailto:james@bond.co.uk
50 ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;RSVP=TRUE;X-PM-TOKEN=123;CN
51 =Dr No.:mailto:dr.no@mi6.co.uk
52 ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=NON-PARTICIPANT;RSVP=FALSE;CN=Miss Moneypen
53 ny:mailto:moneypenny@mi6.co.uk
56 const valarm = `BEGIN:VALARM
62 const valarmInVevent = `BEGIN:VEVENT
63 UID:7E018059-2165-4170-B32F-6936E88E61E5
64 DTSTAMP:20190719T110000Z
65 DTSTART:20190719T120000Z
66 DTEND:20190719T130000Z
73 const veventRruleDaily1 = `BEGIN:VEVENT
74 UID:7E018059-2165-4170-B32F-6936E88E61E5
75 DTSTART:20190719T120000Z
76 DTEND:20190719T130000Z
77 RRULE:FREQ=DAILY;COUNT=10;INTERVAL=3
80 const veventRruleDaily2 = `BEGIN:VEVENT
81 UID:7E018059-2165-4170-B32F-6936E88E61E5
82 DTSTART;VALUE=DATE:20190719
83 DTEND;VALUE=DATE:20190719
84 RRULE:FREQ=DAILY;UNTIL=20200130
87 const veventRruleDaily3 = `BEGIN:VEVENT
88 UID:7E018059-2165-4170-B32F-6936E88E61E5
89 DTSTART:20190719T120000Z
90 DTEND:20190719T130000Z
91 RRULE:FREQ=DAILY;UNTIL=20200130T225959Z
94 const veventRruleDaily4 = `BEGIN:VEVENT
95 UID:7E018059-2165-4170-B32F-6936E88E61E5
96 DTSTART;TZID=America/New_York:20190719T120000
97 DTEND:20190719T130000Z
98 RRULE:FREQ=DAILY;UNTIL=20200130T225959Z
101 const veventsRruleDaily = [veventRruleDaily1, veventRruleDaily2, veventRruleDaily3, veventRruleDaily4];
103 const veventRruleWeekly1 = `BEGIN:VEVENT
104 UID:7E018059-2165-4170-B32F-6936E88E61E5
105 DTSTART:20190719T120000Z
106 DTEND:20190719T130000Z
107 RRULE:FREQ=WEEKLY;COUNT=10;INTERVAL=3;BYDAY=WE,TH
110 const veventRruleWeekly2 = `BEGIN:VEVENT
111 UID:7E018059-2165-4170-B32F-6936E88E61E5
112 DTSTART;VALUE=DATE:20190719
113 DTEND;VALUE=DATE:20190719
114 RRULE:FREQ=WEEKLY;BYDAY=MO;UNTIL=20200130
117 const veventRruleWeekly3 = `BEGIN:VEVENT
118 UID:7E018059-2165-4170-B32F-6936E88E61E5
119 DTSTART:20190719T120000Z
120 DTEND:20190719T130000Z
121 RRULE:FREQ=WEEKLY;BYDAY=MO;UNTIL=20200130T225959Z
124 const veventRruleWeekly4 = `BEGIN:VEVENT
125 UID:7E018059-2165-4170-B32F-6936E88E61E5
126 DTSTART;TZID=America/New_York:20190719T120000
127 DTEND:20190719T130000Z
128 RRULE:FREQ=WEEKLY;BYDAY=MO;UNTIL=20200130T225959Z
131 const veventsRruleWeekly = [veventRruleWeekly1, veventRruleWeekly2, veventRruleWeekly3, veventRruleWeekly4];
133 const veventRruleMonthly1 = `BEGIN:VEVENT
134 UID:7E018059-2165-4170-B32F-6936E88E61E5
135 DTSTART:20190719T120000Z
136 DTEND:20190719T130000Z
137 RRULE:FREQ=MONTHLY;INTERVAL=2;BYMONTHDAY=13;UNTIL=20200130T230000Z
140 const veventRruleMonthly2 = `BEGIN:VEVENT
141 UID:7E018059-2165-4170-B32F-6936E88E61E5
142 DTSTART:20190719T120000Z
143 DTEND:20190719T130000Z
144 RRULE:FREQ=MONTHLY;COUNT=4;BYSETPOS=2;BYDAY=TU
147 const veventRruleMonthly3 = `BEGIN:VEVENT
148 UID:7E018059-2165-4170-B32F-6936E88E61E5
149 DTSTART;VALUE=DATE:20190719
150 DTEND;VALUE=DATE:20190719
151 RRULE:FREQ=MONTHLY;BYSETPOS=-1;BYDAY=MO;UNTIL=20200130
154 const veventRruleMonthly4 = `BEGIN:VEVENT
155 UID:7E018059-2165-4170-B32F-6936E88E61E5
156 DTSTART;TZID=America/New_York:20190719T120000
157 DTEND:20190719T130000Z
158 RRULE:FREQ=MONTHLY;BYMONTHDAY=2;UNTIL=20200130T225959Z
161 const veventsRruleMonthly = [veventRruleMonthly1, veventRruleMonthly2, veventRruleMonthly3, veventRruleMonthly4];
163 const veventRruleYearly1 = `BEGIN:VEVENT
164 UID:7E018059-2165-4170-B32F-6936E88E61E5
165 DTSTART:20190719T120000Z
166 DTEND:20190719T130000Z
167 RRULE:FREQ=YEARLY;COUNT=4;BYMONTH=7;BYMONTHDAY=25
170 const veventRruleYearly2 = `BEGIN:VEVENT
171 UID:7E018059-2165-4170-B32F-6936E88E61E5
172 DTSTART;VALUE=DATE:20190719
173 DTEND;VALUE=DATE:20190719
174 RRULE:FREQ=YEARLY;INTERVAL=2;UNTIL=20200130
177 const veventRruleYearly3 = `BEGIN:VEVENT
178 UID:7E018059-2165-4170-B32F-6936E88E61E5
179 DTSTART:20190719T120000Z
180 DTEND:20190719T130000Z
181 RRULE:FREQ=YEARLY;INTERVAL=2;BYMONTH=7;BYMONTHDAY=25;UNTIL=20200130T225959Z
184 const veventRruleYearly4 = `BEGIN:VEVENT
185 UID:7E018059-2165-4170-B32F-6936E88E61E5
186 DTSTART;TZID=America/New_York:20190719T120000
187 DTEND:20190719T130000Z
188 RRULE:FREQ=YEARLY;UNTIL=20200130T225959Z
191 const veventsRruleYearly = [veventRruleYearly1, veventRruleYearly2, veventRruleYearly3, veventRruleYearly4];
193 const vfreebusy = `BEGIN:VFREEBUSY
194 UID:19970901T095957Z-76A912@example.com
195 ORGANIZER:mailto:jane_doe@example.com
196 ATTENDEE:mailto:john_public@example.com
197 DTSTAMP:19970901T100000Z
198 FREEBUSY:19971015T050000Z/PT8H30M,19971015T160000Z/PT5H30M,19971015T223000Z/PT6H30M
199 URL:http://example.com/pub/busy/jpublic-01.ifb
200 COMMENT:This iCalendar file contains busy time information for the next three months.
203 const vfreebusy2 = `BEGIN:VFREEBUSY
204 UID:19970901T115957Z-76A912@example.com
205 DTSTAMP:19970901T120000Z
206 ORGANIZER:jsmith@example.com
207 DTSTART:19980313T141711Z
208 DTEND:19980410T141711Z
209 FREEBUSY:19980314T233000Z/19980315T003000Z
210 FREEBUSY:19980316T153000Z/19980316T163000Z
211 FREEBUSY:19980318T030000Z/19980318T040000Z
212 URL:http://www.example.com/calendar/busytime/jsmith.ifb
215 const veventWithTrueBoolean = `BEGIN:VEVENT
216 X-PM-PROTON-REPLY;VALUE=BOOLEAN:TRUE
219 const veventWithFalseBoolean = `BEGIN:VEVENT
220 X-PM-PROTON-REPLY;VALUE=BOOLEAN:FALSE
223 const veventWithRandomBoolean = `BEGIN:VEVENT
224 X-PM-PROTON-REPLY;VALUE=BOOLEAN:GNEEEE
227 const veventWithInvalidVAlarm = `
229 DESCRIPTION;LANGUAGE=en-US:\n\n\n
230 UID:040000008200E00074C5B7101A82E00800000000B058B6A2A081D901000000000000000
231 0100000004A031FE80ACD7C418A7A1762749176F121
232 SUMMARY:Calendar test
233 DTSTART;TZID=Eastern Standard Time:20230513T123000
234 DTEND;TZID=Eastern Standard Time:20230513T130000
237 DTSTAMP:20230508T153204Z
241 LOCATION;LANGUAGE=en-US:
244 TRIGGER;RELATED=START:P
250 describe('calendar', () => {
251 it('should parse vcalendar', () => {
252 const result = parse(`BEGIN:VCALENDAR
253 PRODID:-//Google Inc//Google Calendar 70.9054//EN
258 X-WR-TIMEZONE:Europe/Vilnius
260 expect(result).toEqual({
261 component: 'vcalendar',
266 value: '-//Google Inc//Google Calendar 70.9054//EN',
275 value: 'Europe/Vilnius',
283 it('should parse vevent', () => {
284 const result = parse(vevent);
286 expect(result).toEqual({
289 value: '7E018059-2165-4170-B32F-6936E88E61E5',
292 value: { year: 2019, month: 7, day: 19, hours: 13, minutes: 8, seconds: 54, isUTC: true },
295 value: { year: 2019, month: 7, day: 19, hours: 12, minutes: 0, seconds: 0, isUTC: false },
296 parameters: { tzid: 'America/New_York' },
299 value: { year: 2019, month: 7, day: 19, hours: 13, minutes: 0, seconds: 0, isUTC: false },
300 parameters: { tzid: 'Europe/Zurich' },
307 value: ['ANNIVERSARY', 'PERSONAL', 'SPECIAL OCCASION'],
311 value: 'Our Blissful Anniversary',
316 it('should parse Boolean properties', () => {
317 expect(parse(veventWithTrueBoolean)).toEqual({
319 'x-pm-proton-reply': { value: 'true', parameters: { type: 'boolean' } },
321 expect(parse(veventWithFalseBoolean)).toEqual({
323 'x-pm-proton-reply': { value: 'false', parameters: { type: 'boolean' } },
325 expect(parse(veventWithRandomBoolean)).toEqual({
327 'x-pm-proton-reply': { value: 'false', parameters: { type: 'boolean' } },
331 it('should parse valarm', () => {
332 const result = parse(valarm) as VcalValarmComponent;
334 expect(result).toEqual({
337 value: { weeks: 0, days: 0, hours: 15, minutes: 0, seconds: 0, isNegative: true },
348 it('should parse vfreebusy2', () => {
349 expect(parse(vfreebusy2)).toEqual({
350 component: 'vfreebusy',
352 value: '19970901T115957Z-76A912@example.com',
355 value: { year: 1997, month: 9, day: 1, hours: 12, minutes: 0, seconds: 0, isUTC: true },
358 value: 'jsmith@example.com',
457 value: 'http://www.example.com/calendar/busytime/jsmith.ifb',
462 it('should parse vfreebusy', () => {
463 expect(parse(vfreebusy)).toEqual({
464 component: 'vfreebusy',
466 value: '19970901T095957Z-76A912@example.com',
469 value: { year: 1997, month: 9, day: 1, hours: 10, minutes: 0, seconds: 0, isUTC: true },
472 value: 'mailto:jane_doe@example.com',
476 value: 'mailto:john_public@example.com',
483 start: { year: 1997, month: 10, day: 15, hours: 5, minutes: 0, seconds: 0, isUTC: true },
484 duration: { weeks: 0, days: 0, hours: 8, minutes: 30, seconds: 0, isNegative: false },
487 start: { year: 1997, month: 10, day: 15, hours: 16, minutes: 0, seconds: 0, isUTC: true },
488 duration: { weeks: 0, days: 0, hours: 5, minutes: 30, seconds: 0, isNegative: false },
491 start: { year: 1997, month: 10, day: 15, hours: 22, minutes: 30, seconds: 0, isUTC: true },
492 duration: { weeks: 0, days: 0, hours: 6, minutes: 30, seconds: 0, isNegative: false },
499 value: 'This iCalendar file contains busy time information for the next three months.',
503 value: 'http://example.com/pub/busy/jpublic-01.ifb',
508 it('should parse all day vevent', () => {
509 const { dtstart } = parse(allDayVevent) as VcalVeventComponent;
511 expect(dtstart).toEqual({
512 value: { year: 2019, month: 8, day: 12 },
513 parameters: { type: 'date' },
517 it('should parse vevent with recurrence id', () => {
518 const { dtstart, 'recurrence-id': recurrenceId } = parse(veventWithRecurrenceId) as VcalVeventComponent;
520 expect(recurrenceId).toEqual({
521 value: { year: 2020, month: 3, day: 11, hours: 10, minutes: 0, seconds: 0, isUTC: false },
522 parameters: { tzid: 'Europe/Zurich' },
525 expect(dtstart).toEqual({
526 value: { year: 2020, month: 3, day: 11, hours: 10, minutes: 0, seconds: 0, isUTC: false },
527 parameters: { tzid: 'Europe/Zurich' },
531 it('should parse valarm in vevent', () => {
532 const component = parse(valarmInVevent) as VcalVeventComponent;
534 expect(component).toEqual({
539 action: { value: 'DISPLAY' },
541 value: { weeks: 0, days: 0, hours: 15, minutes: 0, seconds: 0, isNegative: true },
546 value: '7E018059-2165-4170-B32F-6936E88E61E5',
549 value: { year: 2019, month: 7, day: 19, hours: 11, minutes: 0, seconds: 0, isUTC: true },
552 value: { year: 2019, month: 7, day: 19, hours: 12, minutes: 0, seconds: 0, isUTC: true },
555 value: { year: 2019, month: 7, day: 19, hours: 13, minutes: 0, seconds: 0, isUTC: true },
560 it('should parse daily rrule in vevent', () => {
561 const components = veventsRruleDaily.map(parse);
563 expect(components).toEqual([
567 value: '7E018059-2165-4170-B32F-6936E88E61E5',
570 value: { year: 2019, month: 7, day: 19, hours: 12, minutes: 0, seconds: 0, isUTC: true },
573 value: { year: 2019, month: 7, day: 19, hours: 13, minutes: 0, seconds: 0, isUTC: true },
586 value: '7E018059-2165-4170-B32F-6936E88E61E5',
589 value: { year: 2019, month: 7, day: 19 },
590 parameters: { type: 'date' },
593 value: { year: 2019, month: 7, day: 19 },
594 parameters: { type: 'date' },
599 until: { year: 2020, month: 1, day: 30 },
606 value: '7E018059-2165-4170-B32F-6936E88E61E5',
609 value: { year: 2019, month: 7, day: 19, hours: 12, minutes: 0, seconds: 0, isUTC: true },
612 value: { year: 2019, month: 7, day: 19, hours: 13, minutes: 0, seconds: 0, isUTC: true },
617 until: { year: 2020, month: 1, day: 30, hours: 22, minutes: 59, seconds: 59, isUTC: true },
624 value: '7E018059-2165-4170-B32F-6936E88E61E5',
627 value: { year: 2019, month: 7, day: 19, hours: 12, minutes: 0, seconds: 0, isUTC: false },
628 parameters: { tzid: 'America/New_York' },
631 value: { year: 2019, month: 7, day: 19, hours: 13, minutes: 0, seconds: 0, isUTC: true },
636 until: { year: 2020, month: 1, day: 30, hours: 22, minutes: 59, seconds: 59, isUTC: true },
643 it('should parse weekly rrule in vevent', () => {
644 const components = veventsRruleWeekly.map(parse);
646 expect(components).toEqual([
650 value: '7E018059-2165-4170-B32F-6936E88E61E5',
653 value: { year: 2019, month: 7, day: 19, hours: 12, minutes: 0, seconds: 0, isUTC: true },
656 value: { year: 2019, month: 7, day: 19, hours: 13, minutes: 0, seconds: 0, isUTC: true },
670 value: '7E018059-2165-4170-B32F-6936E88E61E5',
673 value: { year: 2019, month: 7, day: 19 },
674 parameters: { type: 'date' },
677 value: { year: 2019, month: 7, day: 19 },
678 parameters: { type: 'date' },
684 until: { year: 2020, month: 1, day: 30 },
691 value: '7E018059-2165-4170-B32F-6936E88E61E5',
694 value: { year: 2019, month: 7, day: 19, hours: 12, minutes: 0, seconds: 0, isUTC: true },
697 value: { year: 2019, month: 7, day: 19, hours: 13, minutes: 0, seconds: 0, isUTC: true },
703 until: { year: 2020, month: 1, day: 30, hours: 22, minutes: 59, seconds: 59, isUTC: true },
710 value: '7E018059-2165-4170-B32F-6936E88E61E5',
713 value: { year: 2019, month: 7, day: 19, hours: 12, minutes: 0, seconds: 0, isUTC: false },
714 parameters: { tzid: 'America/New_York' },
717 value: { year: 2019, month: 7, day: 19, hours: 13, minutes: 0, seconds: 0, isUTC: true },
723 until: { year: 2020, month: 1, day: 30, hours: 22, minutes: 59, seconds: 59, isUTC: true },
730 it('should parse monthly rrule in vevent', () => {
731 const components = veventsRruleMonthly.map(parse);
733 expect(components).toEqual([
737 value: '7E018059-2165-4170-B32F-6936E88E61E5',
740 value: { year: 2019, month: 7, day: 19, hours: 12, minutes: 0, seconds: 0, isUTC: true },
743 value: { year: 2019, month: 7, day: 19, hours: 13, minutes: 0, seconds: 0, isUTC: true },
750 until: { year: 2020, month: 1, day: 30, hours: 23, minutes: 0, seconds: 0, isUTC: true },
757 value: '7E018059-2165-4170-B32F-6936E88E61E5',
760 value: { year: 2019, month: 7, day: 19, hours: 12, minutes: 0, seconds: 0, isUTC: true },
763 value: { year: 2019, month: 7, day: 19, hours: 13, minutes: 0, seconds: 0, isUTC: true },
777 value: '7E018059-2165-4170-B32F-6936E88E61E5',
780 value: { year: 2019, month: 7, day: 19 },
781 parameters: { type: 'date' },
784 value: { year: 2019, month: 7, day: 19 },
785 parameters: { type: 'date' },
792 until: { year: 2020, month: 1, day: 30 },
799 value: '7E018059-2165-4170-B32F-6936E88E61E5',
802 value: { year: 2019, month: 7, day: 19, hours: 12, minutes: 0, seconds: 0, isUTC: false },
803 parameters: { tzid: 'America/New_York' },
806 value: { year: 2019, month: 7, day: 19, hours: 13, minutes: 0, seconds: 0, isUTC: true },
812 until: { year: 2020, month: 1, day: 30, hours: 22, minutes: 59, seconds: 59, isUTC: true },
819 it('should parse yearly rrule in vevent', () => {
820 const components = veventsRruleYearly.map(parse);
822 expect(components).toEqual([
826 value: '7E018059-2165-4170-B32F-6936E88E61E5',
829 value: { year: 2019, month: 7, day: 19, hours: 12, minutes: 0, seconds: 0, isUTC: true },
832 value: { year: 2019, month: 7, day: 19, hours: 13, minutes: 0, seconds: 0, isUTC: true },
846 value: '7E018059-2165-4170-B32F-6936E88E61E5',
849 value: { year: 2019, month: 7, day: 19 },
850 parameters: { type: 'date' },
853 value: { year: 2019, month: 7, day: 19 },
854 parameters: { type: 'date' },
860 until: { year: 2020, month: 1, day: 30 },
867 value: '7E018059-2165-4170-B32F-6936E88E61E5',
870 value: { year: 2019, month: 7, day: 19, hours: 12, minutes: 0, seconds: 0, isUTC: true },
873 value: { year: 2019, month: 7, day: 19, hours: 13, minutes: 0, seconds: 0, isUTC: true },
881 until: { year: 2020, month: 1, day: 30, hours: 22, minutes: 59, seconds: 59, isUTC: true },
888 value: '7E018059-2165-4170-B32F-6936E88E61E5',
891 value: { year: 2019, month: 7, day: 19, hours: 12, minutes: 0, seconds: 0, isUTC: false },
892 parameters: { tzid: 'America/New_York' },
895 value: { year: 2019, month: 7, day: 19, hours: 13, minutes: 0, seconds: 0, isUTC: true },
900 until: { year: 2020, month: 1, day: 30, hours: 22, minutes: 59, seconds: 59, isUTC: true },
907 it('should parse attendees in vevent', () => {
908 const component = parse(veventWithAttendees);
910 expect(component).toEqual({
913 value: '7E018059-2165-4170-B32F-6936E88E61E5',
916 value: { year: 2019, month: 8, day: 12 },
917 parameters: { type: 'date' },
920 value: { year: 2019, month: 8, day: 13 },
921 parameters: { type: 'date' },
928 value: 'mailto:james@bond.co.uk',
930 cutype: 'INDIVIDUAL',
931 role: 'REQ-PARTICIPANT',
934 cn: 'james@bond.co.uk',
938 value: 'mailto:dr.no@mi6.co.uk',
940 cutype: 'INDIVIDUAL',
941 role: 'REQ-PARTICIPANT',
948 value: 'mailto:moneypenny@mi6.co.uk',
950 cutype: 'INDIVIDUAL',
951 role: 'NON-PARTICIPANT',
953 cn: 'Miss Moneypenny',
960 const trimAll = (str: string) => str.replace(/\r?\n ?|\r/g, '');
962 it('should round trip valarm in vevent', () => {
963 const result = serialize(parse(valarmInVevent));
964 expect(trimAll(result)).toEqual(trimAll(valarmInVevent));
967 it('should round trip vfreebusy', () => {
968 const result = serialize(parse(vfreebusy));
969 expect(trimAll(result)).toEqual(trimAll(vfreebusy));
972 it('should round trip vfreebusy2', () => {
973 const result = serialize(parse(vfreebusy2));
974 expect(trimAll(result)).toEqual(trimAll(vfreebusy2));
977 it('should round trip rrule in vevent', () => {
978 const vevents = [...veventsRruleDaily, ...veventsRruleWeekly, ...veventsRruleMonthly, ...veventsRruleYearly];
979 const results = vevents.map((vevent) => serialize(parse(vevent)));
980 expect(results.map(trimAll)).toEqual(vevents.map(trimAll));
983 it('should round trip vevent', () => {
984 const result = serialize(parse(vevent));
985 expect(trimAll(result)).toEqual(trimAll(vevent));
988 it('should round trip vevent with recurrence-id', () => {
989 const result = serialize(parse(veventWithRecurrenceId));
990 expect(trimAll(result)).toEqual(trimAll(veventWithRecurrenceId));
993 it('should round trip vevent with attendees', () => {
994 const result = serialize(parse(veventWithAttendees));
995 expect(trimAll(result)).toEqual(trimAll(veventWithAttendees));
998 it('should round trip all day vevent', () => {
999 const result = serialize(parse(allDayVevent));
1000 expect(trimAll(result)).toEqual(trimAll(allDayVevent));
1003 it('should normalize exdate', () => {
1004 const veventWithExdate = `BEGIN:VEVENT
1005 RRULE:FREQ=DAILY;COUNT=6
1006 DTSTART;TZID=Europe/Zurich:20200309T043000
1007 DTEND;TZID=Europe/Zurich:20200309T063000
1008 EXDATE:19960402T010000Z,19960403T010000Z,19960404T010000Z
1009 EXDATE;TZID=Europe/Zurich:20200311T043000
1010 EXDATE;TZID=Europe/Zurich:20200313T043000
1011 EXDATE;VALUE=DATE:20200311
1014 const normalizedVevent = `BEGIN:VEVENT
1015 RRULE:FREQ=DAILY;COUNT=6
1016 DTSTART;TZID=Europe/Zurich:20200309T043000
1017 DTEND;TZID=Europe/Zurich:20200309T063000
1018 EXDATE:19960402T010000Z
1019 EXDATE:19960403T010000Z
1020 EXDATE:19960404T010000Z
1021 EXDATE;TZID=Europe/Zurich:20200311T043000
1022 EXDATE;TZID=Europe/Zurich:20200313T043000
1023 EXDATE;VALUE=DATE:20200311
1025 const result = serialize(parse(veventWithExdate));
1026 expect(trimAll(result)).toEqual(trimAll(normalizedVevent));
1029 it('should parse trigger string', () => {
1030 expect(fromTriggerString('-PT30M')).toEqual({
1040 it('should convert trigger strings into milliseconds', () => {
1041 expect(getMillisecondsFromTriggerString('-PT30M')).toEqual(-30 * MINUTE);
1042 expect(getMillisecondsFromTriggerString('PT1H')).toEqual(HOUR);
1043 expect(getMillisecondsFromTriggerString('-P1D')).toEqual(-DAY);
1044 expect(getMillisecondsFromTriggerString('-PT2H34M12S')).toEqual(-2 * HOUR - 34 * MINUTE - 12 * SECOND);
1045 expect(getMillisecondsFromTriggerString('P2W1DT1S')).toEqual(2 * WEEK + DAY + SECOND);
1048 it('should filter out invalid vAlarm', () => {
1049 const parsed = parseVcalendarWithRecoveryAndMaybeErrors(
1050 veventWithInvalidVAlarm
1051 ) as VcalVeventComponentWithMaybeErrors;
1052 expect(getVeventWithoutErrors(parsed)).toEqual({
1053 component: 'vevent',
1055 description: { value: '', parameters: { language: 'en-US' } },
1057 value: '040000008200E00074C5B7101A82E00800000000B058B6A2A081D901000000000000000 0100000004A031FE80ACD7C418A7A1762749176F121',
1060 value: 'Calendar test',
1063 value: { year: 2023, month: 5, day: 13, hours: 12, minutes: 30, seconds: 0, isUTC: false },
1064 parameters: { tzid: 'Eastern Standard Time' },
1067 value: { year: 2023, month: 5, day: 13, hours: 13, minutes: 0, seconds: 0, isUTC: false },
1069 tzid: 'Eastern Standard Time',
1072 class: { value: 'PUBLIC' },
1073 priority: { value: 5 },
1075 value: { year: 2023, month: 5, day: 8, hours: 15, minutes: 32, seconds: 4, isUTC: true },
1077 transp: { value: 'OPAQUE' },
1078 status: { value: 'CONFIRMED' },
1079 sequence: { value: 0 },
1080 location: { value: '', parameters: { language: 'en-US' } },
1085 describe('parseVcalendarWithRecoveryAndMaybeErrors', () => {
1086 it('should add missing mandatory properties', () => {
1087 const ics = `BEGIN:VCALENDAR
1089 const result = parseVcalendarWithRecoveryAndMaybeErrors(ics) as VcalVcalendarWithMaybeErrors;
1091 expect(result.component).toEqual('vcalendar');
1092 expect(result.version.value).toEqual('2.0');
1093 expect(result.prodid.value).toEqual('');
1096 it('should catch errors from badly formatted all-day dates (with recovery for those off)', () => {
1097 const ics = `BEGIN:VCALENDAR
1100 DTSTAMP:20200405T143241Z
1104 const result = parseVcalendarWithRecoveryAndMaybeErrors(ics, {
1105 retryDateTimes: false,
1106 }) as VcalVcalendarWithMaybeErrors;
1108 expect(result.component).toEqual('vcalendar');
1109 expect((result.components as VcalErrorComponent[])[0].error).toMatch('invalid date-time value');
1112 it('should catch errors from badly formatted date-times (with recovery for those off)', () => {
1113 const ics = `BEGIN:VCALENDAR
1114 X-LOTUS-CHARSET:UTF-8
1116 PRODID:http://www.bahn.de
1119 UID:bahn2023-06-21082400
1121 SUMMARY:Hamburg Hbf -> Paris Est
1122 DTSTART;TZID=Europe/Berlin:2023-06-21T082400
1123 DTEND;TZID=Europe/Berlin:2023-06-21T165400
1124 DTSTAMP:2023-06-13T212500Z
1128 const result = parseVcalendarWithRecoveryAndMaybeErrors(ics, {
1129 retryDateTimes: false,
1130 }) as VcalVcalendarWithMaybeErrors;
1132 expect(result.component).toEqual('vcalendar');
1133 expect((result.components as VcalErrorComponent[])[0].error).toMatch('invalid date-time value');
1136 it('should catch errors from badly formatted dates (with recovery for those off)', () => {
1137 const ics = `BEGIN:VCALENDAR
1138 X-LOTUS-CHARSET:UTF-8
1140 PRODID:http://www.bahn.de
1143 UID:bahn2023-06-21082400
1145 SUMMARY:Hamburg Hbf -> Paris Est
1146 DTSTART;VALUE=DATE:2023-06-21
1147 DTEND;VALUE=DATE:2023-06-21
1148 DTSTAMP:20230613T212500Z
1152 const result = parseVcalendarWithRecoveryAndMaybeErrors(ics, {
1153 retryDateTimes: false,
1154 }) as VcalVcalendarWithMaybeErrors;
1156 expect(result.component).toEqual('vcalendar');
1157 expect((result.components as VcalErrorComponent[])[0].error).toMatch('could not extract integer');