1 import { FREQUENCY } from '../../../lib/calendar/constants';
12 } from '../../../lib/calendar/recurrence/rrule';
13 import { parse } from '../../../lib/calendar/vcal';
15 describe('getIsStandardByday', () => {
16 it('returns true for standard BYDAY strings', () => {
17 const bydays = ['SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'];
18 expect(bydays.map(getIsStandardByday)).toEqual(bydays.map(() => true));
21 it('returns false for non-standard BYDAY strings', () => {
22 const bydays = ['1SU', 'SUN', 'S', 'DO', ''];
23 expect(bydays.map(getIsStandardByday)).toEqual(bydays.map(() => false));
27 describe('getDayAndSetpos', () => {
28 it('gets the day and setpos correctly', () => {
29 const bydays = ['1SU', '+2MO', '-1FR', 'WE'];
30 const results = [{ setpos: 1, day: 'SU' }, { setpos: 2, day: 'MO' }, { setpos: -1, day: 'FR' }, { day: 'WE' }];
31 expect(bydays.map((byday) => getDayAndSetpos(byday))).toEqual(results);
35 describe('getSupportedUntil', () => {
36 it('should keep the date for all-day events', () => {
37 const tzid = 'Pacific/Niue';
38 const until = { year: 2020, month: 6, day: 27, hours: 10, minutes: 59, seconds: 59, isUTC: true };
39 const dtstartValue = { year: 2010, month: 6, day: 27 };
40 const expected = { year: 2020, month: 6, day: 27 };
44 dtstart: { parameters: { type: 'date' }, value: dtstartValue },
50 it('should always return the same until if it was at the end of the day already', () => {
51 const tzid = 'Pacific/Niue';
52 const until = { year: 2020, month: 6, day: 27, hours: 10, minutes: 59, seconds: 59, isUTC: true };
53 const dtstartValue = { year: 2010, month: 6, day: 27, hours: 10, minutes: 59, seconds: 59, isUTC: false };
54 expect(getSupportedUntil({ until, dtstart: { parameters: { tzid }, value: dtstartValue } })).toEqual(until);
57 it('should always return an until at the end of the day', () => {
58 const tzid = 'Pacific/Niue';
60 { year: 2020, month: 6, day: 27, hours: 15, minutes: 14, isUTC: true },
61 { year: 2020, month: 6, day: 27 },
64 { year: 2020, month: 6, day: 28, hours: 10, minutes: 59, seconds: 59, isUTC: true },
65 { year: 2020, month: 6, day: 27, hours: 10, minutes: 59, seconds: 59, isUTC: true },
67 const dtstartValue = { year: 2010, month: 6, day: 28, hours: 10, minutes: 59, seconds: 59, isUTC: false };
69 untils.map((until) => getSupportedUntil({ until, dtstart: { parameters: { tzid }, value: dtstartValue } }))
73 it('should keep the right date for all-day events when a guess tzid is passed', () => {
74 const guessTzid = 'Asia/Taipei';
75 const until = { year: 2020, month: 11, day: 24, hours: 16, minutes: 0, seconds: 0, isUTC: true };
76 const dtstartValue = { year: 2010, month: 11, day: 24 };
77 const expected = { year: 2020, month: 11, day: 25 };
81 dtstart: { parameters: { type: 'date' }, value: dtstartValue },
88 describe('getIsRruleSimple', () => {
89 it('returns true for simple recurring events', () => {
92 freq: FREQUENCY.DAILY,
96 freq: FREQUENCY.WEEKLY,
101 freq: FREQUENCY.MONTHLY,
106 freq: FREQUENCY.YEARLY,
111 freq: FREQUENCY.YEARLY,
116 expect(rrules.map(getIsRruleSimple)).toEqual(rrules.map(() => true));
119 it('returns false for supported custom-recurring events', () => {
122 freq: FREQUENCY.DAILY,
127 freq: FREQUENCY.WEEKLY,
131 freq: FREQUENCY.WEEKLY,
136 freq: FREQUENCY.WEEKLY,
137 until: { year: 2020, month: 5, day: 10 },
140 freq: FREQUENCY.MONTHLY,
145 freq: FREQUENCY.MONTHLY,
147 until: { year: 2020, month: 5, day: 10 },
150 freq: FREQUENCY.YEARLY,
154 freq: FREQUENCY.YEARLY,
158 expect(rrules.map(getIsRruleSimple)).toEqual(rrules.map(() => false));
161 it('returns false for non-supported custom-recurring events', () => {
167 freq: FREQUENCY.WEEKLY,
172 freq: FREQUENCY.WEEKLY,
177 freq: FREQUENCY.MONTHLY,
181 freq: FREQUENCY.YEARLY,
187 freq: FREQUENCY.YEARLY,
192 expect(rrules.map(getIsRruleSimple)).toEqual(rrules.map(() => false));
196 describe('getIsRruleCustom', () => {
197 it('returns false for simple recurring events', () => {
200 freq: FREQUENCY.DAILY,
204 freq: FREQUENCY.WEEKLY,
209 freq: FREQUENCY.MONTHLY,
214 freq: FREQUENCY.YEARLY,
219 freq: FREQUENCY.YEARLY,
224 expect(rrules.map(getIsRruleCustom)).toEqual(rrules.map(() => false));
227 it('returns true for supported custom-recurring events', () => {
230 freq: FREQUENCY.DAILY,
235 freq: FREQUENCY.WEEKLY,
239 freq: FREQUENCY.WEEKLY,
244 freq: FREQUENCY.WEEKLY,
245 until: { year: 2020, month: 5, day: 10 },
248 freq: FREQUENCY.MONTHLY,
253 freq: FREQUENCY.MONTHLY,
255 until: { year: 2020, month: 5, day: 10 },
258 freq: FREQUENCY.YEARLY,
262 freq: FREQUENCY.YEARLY,
266 expect(rrules.map(getIsRruleCustom)).toEqual(rrules.map(() => true));
269 it('returns false for non-supported custom-recurring events', () => {
272 freq: FREQUENCY.DAILY,
278 freq: FREQUENCY.WEEKLY,
284 freq: FREQUENCY.WEEKLY,
285 until: { year: 2020, month: 5, day: 10 },
289 freq: FREQUENCY.MONTHLY,
293 freq: FREQUENCY.MONTHLY,
297 expect(rrules.map(getIsRruleCustom)).toEqual(rrules.map(() => false));
301 describe('getHasOccurrences', () => {
302 it('should filter out events that generate no occurrence', () => {
305 value: { year: 2015, month: 8, day: 25, hours: 18, minutes: 30, seconds: 0, isUTC: false },
307 tzid: 'Europe/Paris',
318 value: { year: 2015, month: 8, day: 25, hours: 18, minutes: 30, seconds: 0, isUTC: false },
320 tzid: 'Europe/Paris',
324 value: { year: 2015, month: 8, day: 26, hours: 18, minutes: 30, seconds: 0, isUTC: false },
326 tzid: 'Europe/Paris',
331 expect(getHasOccurrences(vevent)).toEqual(false);
335 describe('getIsRruleSupported', () => {
336 it('should accept events with monthly recurring rules of the form BYDAY=+/-nDD', () => {
338 `BEGIN:VEVENT\r\nRRULE:FREQ=MONTHLY;BYDAY=1SU\r\nEND:VEVENT`,
339 `BEGIN:VEVENT\r\nRRULE:FREQ=MONTHLY;BYDAY=+2WE\r\nEND:VEVENT`,
340 `BEGIN:VEVENT\r\nRRULE:FREQ=MONTHLY;COUNT=1;BYDAY=4MO\r\nEND:VEVENT`,
341 `BEGIN:VEVENT\r\nRRULE:FREQ=MONTHLY;BYDAY=-1SA\r\nEND:VEVENT`,
342 `BEGIN:VEVENT\r\nRRULE:FREQ=MONTHLY;COUNT=3;INTERVAL=2;BYDAY=TH;BYSETPOS=-1\r\nEND:VEVENT`,
343 `BEGIN:VEVENT\r\nRRULE:FREQ=MONTHLY;BYDAY=TU;BYSETPOS=3\r\nEND:VEVENT`,
345 const rrules = vevents.map((vevent) => {
346 const { rrule } = parse(vevent);
349 expect(rrules.map((rrule) => getIsRruleSupported(rrule))).toEqual(vevents.map(() => true));
352 it('should accept events with custom recurring rules for invitations that are not supported via import', () => {
354 `BEGIN:VEVENT\r\nRRULE:FREQ=DAILY;BYHOUR=9,11;BYMINUTE=30\r\nEND:VEVENT`,
355 `BEGIN:VEVENT\r\nRRULE:FREQ=WEEKLY;BYMONTH=1,2,3;BYHOUR=9\r\nEND:VEVENT`,
356 `BEGIN:VEVENT\r\nRRULE:FREQ=MONTHLY;BYMONTHDAY=8,9,10,-1\r\nEND:VEVENT`,
357 `BEGIN:VEVENT\r\nRRULE:FREQ=YEARLY;BYWEEKNO=50;BYMONTH=12\r\nEND:VEVENT`,
359 const rrules = vevents.map((vevent) => {
360 const { rrule } = parse(vevent);
363 expect(rrules.map((rrule) => getIsRruleSupported(rrule))).toEqual(vevents.map(() => false));
364 expect(rrules.map((rrule) => getIsRruleSupported(rrule, true))).toEqual(vevents.map(() => true));
367 it('should accept events with valid yearly recurring rules', () => {
369 `BEGIN:VEVENT\r\nRRULE:FREQ=YEARLY;UNTIL=20200330T150000Z;INTERVAL=1;BYMONTHDAY=30;BYMONTH=3\r\nEND:VEVENT`,
370 `BEGIN:VEVENT\r\nRRULE:FREQ=YEARLY;INTERVAL=2;BYMONTH=5\r\nEND:VEVENT`,
371 `BEGIN:VEVENT\r\nRRULE:FREQ=YEARLY;WKST=MO;UNTIL=20160103\r\nEND:VEVENT`,
373 const rrules = vevents.map((vevent) => {
374 const { rrule } = parse(vevent);
377 expect(rrules.map((rrule) => getIsRruleSupported(rrule))).toEqual(vevents.map(() => true));
380 it('should refuse events with invalid monthly recurring rules', () => {
382 `BEGIN:VEVENT\r\nRRULE:FREQ=MONTHLY;BYDAY=1SU,2MO\r\nEND:VEVENT`,
383 `BEGIN:VEVENT\r\nRRULE:FREQ=MONTHLY;BYDAY=-2WE\r\nEND:VEVENT`,
384 `BEGIN:VEVENT\r\nRRULE:FREQ=MONTHLY;BYDAY=5TH\r\nEND:VEVENT`,
385 `BEGIN:VEVENT\r\nRRULE:FREQ=MONTHLY;BYDAY=FR\r\nEND:VEVENT`,
386 `BEGIN:VEVENT\r\nRRULE:FREQ=MONTHLY;BYDAY=FR;BYSETPOS=5\r\nEND:VEVENT`,
387 `BEGIN:VEVENT\r\nRRULE:FREQ=MONTHLY;BYDAY=TU;BYSETPOS=-2\r\nEND:VEVENT`,
388 `BEGIN:VEVENT\r\nRRULE:FREQ=MONTHLY;INTERVAL=2;BYMONTHDAY=10,13,14\r\nEND:VEVENT`,
389 `BEGIN:VEVENT\r\nRRULE:FREQ=MONTHLY;INTERVAL=1;BYSETPOS=1;BYDAY=SU,MO,TU,WE,TH,FR,SA\r\nEND:VEVENT`,
391 const rrules = vevents.map((vevent) => {
392 const { rrule } = parse(vevent);
395 expect(rrules.map((rrule) => getIsRruleSupported(rrule))).toEqual(vevents.map(() => false));
398 it('should refuse events with invalid yearly recurring rules', () => {
400 `BEGIN:VEVENT\r\nRRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=1,2,3,4,5,6,7,8,9,10,11,12\r\nEND:VEVENT`,
401 `BEGIN:VEVENT\r\nRRULE:FREQ=YEARLY;BYMONTHDAY=11,22\r\nEND:VEVENT`,
402 `BEGIN:VEVENT\r\nRRULE:FREQ=YEARLY;INTERVAL=2;BYMONTHDAY=17\r\nEND:VEVENT`,
404 const rrules = vevents.map((vevent) => {
405 const { rrule } = parse(vevent);
408 expect(rrules.map((rrule) => getIsRruleSupported(rrule))).toEqual(vevents.map(() => false));
412 describe('getSupportedRrule', () => {
413 const dtstartPartDayUTC = {
414 value: { year: 2020, month: 5, day: 11, hours: 12, minutes: 0, seconds: 0, isUTC: true },
416 const dtstartPartDayZoned = {
417 value: { year: 2020, month: 5, day: 11, hours: 12, minutes: 0, seconds: 0, isUTC: false },
418 parameters: { tzid: 'Antarctica/Troll' },
420 const dtstartAllDay = {
421 value: { year: 2020, month: 5, day: 11 },
422 parameters: { type: 'date' },
425 it('should reformat rrules with a badly formatted UNTIL', () => {
428 dtstart: dtstartPartDayUTC,
429 rrule: { value: { freq: 'WEEKLY', until: { year: 2020, month: 5, day: 15 } } },
432 dtstart: dtstartPartDayZoned,
433 rrule: { value: { freq: 'WEEKLY', until: { year: 2020, month: 5, day: 15 } } },
436 dtstart: dtstartAllDay,
440 until: { year: 2020, month: 5, day: 15, hours: 12, minutes: 0, seconds: 30, isUTC: false },
449 until: { year: 2020, month: 5, day: 15, hours: 23, minutes: 59, seconds: 59, isUTC: true },
455 until: { year: 2020, month: 5, day: 15, hours: 21, minutes: 59, seconds: 59, isUTC: true },
458 { value: { freq: 'WEEKLY', until: { year: 2020, month: 5, day: 15 } } },
460 expect(vevents.map((vevent) => getSupportedRrule(vevent))).toEqual(expected);
463 it('should reformat rrules with UNTIL in the middle of the day', () => {
466 dtstart: dtstartPartDayUTC,
470 until: { year: 2020, month: 5, day: 15, hours: 12, minutes: 0, seconds: 30, isUTC: true },
475 dtstart: dtstartPartDayZoned,
479 until: { year: 2020, month: 5, day: 15, hours: 12, minutes: 0, seconds: 30, isUTC: true },
488 until: { year: 2020, month: 5, day: 15, hours: 23, minutes: 59, seconds: 59, isUTC: true },
494 until: { year: 2020, month: 5, day: 15, hours: 21, minutes: 59, seconds: 59, isUTC: true },
498 expect(vevents.map((vevent) => getSupportedRrule(vevent))).toEqual(expected);
502 describe('getHasConsistentRrule', () => {
503 const dtstartPartDayUTC = {
504 value: { year: 2020, month: 5, day: 11, hours: 12, minutes: 0, seconds: 0, isUTC: true },
507 it('should not allow count and until together', () => {
509 value: { year: 2015, month: 8, day: 25, hours: 18, minutes: 30, seconds: 0, isUTC: false },
511 tzid: 'Europe/Paris',
514 const until = { year: 2015, month: 8, day: 30, hours: 16, minutes: 29, seconds: 59, isUTC: true };
517 getHasConsistentRrule({
528 getHasConsistentRrule({
539 getHasConsistentRrule({
552 it('should not allow byyearday for frequencies other than yearly', () => {
554 value: { year: 2022, month: 1, day: 27, hours: 18, minutes: 30, seconds: 0, isUTC: false },
556 tzid: 'Europe/Paris',
561 getHasConsistentRrule({
572 getHasConsistentRrule({
585 getHasConsistentRrule({
597 getHasConsistentRrule({
609 it('should filter out inconsistent rrules', () => {
629 const vevents = rrules.map((rrule) => ({
630 dtstart: dtstartPartDayUTC,
631 rrule: { value: rrule },
633 const expected = vevents.map(() => false);
634 expect(vevents.map((vevent) => getHasConsistentRrule(vevent))).toEqual(expected);
637 it('should exclude exdate when checking consistency', () => {
640 value: { year: 2015, month: 8, day: 25, hours: 18, minutes: 30, seconds: 0, isUTC: false },
642 tzid: 'Europe/Paris',
648 until: { year: 2015, month: 8, day: 30, hours: 16, minutes: 29, seconds: 59, isUTC: true },
653 value: { year: 2015, month: 8, day: 25, hours: 18, minutes: 30, seconds: 0, isUTC: false },
655 tzid: 'Europe/Paris',
660 expect(getHasConsistentRrule(vevent)).toEqual(true);
663 it('should not care about COUNT = 0', () => {
666 value: { year: 2015, month: 8, day: 25, hours: 18, minutes: 30, seconds: 0, isUTC: false },
668 tzid: 'Europe/Paris',
678 expect(getHasConsistentRrule(vevent)).toEqual(true);
681 it('should not care about UNTIL earlier than DTSTART', () => {
684 value: { year: 2015, month: 8, day: 25, hours: 18, minutes: 30, seconds: 0, isUTC: false },
686 tzid: 'Europe/Paris',
692 until: { year: 2015, month: 8, day: 25, hours: 16, minutes: 0, seconds: 0, isUTC: true },
696 expect(getHasConsistentRrule(vevent)).toEqual(true);