test(11-03): add failing tests for reminderLeadMinutes on CalendarOccurrence (D-06/D-10)
- non-recurring event: occurrence carries reminderLeadMinutes=30 from master - all-day event with 0-minute trigger: occurrence carries 0 (NULL-vs-0, D-06) - no VALARM: occurrence carries reminderLeadMinutes=null - D-10 series-level: all recurring occurrences inherit master's reminderLeadMinutes=60
This commit is contained in:
@@ -350,3 +350,144 @@ describe('expandOccurrences', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ─── Phase 11 Plan 03 Task 3: reminderLeadMinutes on CalendarOccurrence ─────────
|
||||||
|
// D-06/D-10: reminderLeadMinutes is a series-level property — all occurrences of a
|
||||||
|
// recurring master inherit the master's lead. NULL-vs-0-vs-positive must survive
|
||||||
|
// through expansion.
|
||||||
|
|
||||||
|
describe('expandOccurrences — reminderLeadMinutes propagation (Phase 11 Plan 03 Task 3)', () => {
|
||||||
|
// Helper: minimal VCALENDAR/VEVENT string for tests
|
||||||
|
function makeVevent(overrides: {
|
||||||
|
uid?: string;
|
||||||
|
allDay?: boolean;
|
||||||
|
reminderMinutes?: number | 'absolute' | 'none';
|
||||||
|
rrule?: string;
|
||||||
|
}): string {
|
||||||
|
const uid = overrides.uid ?? 'test-uid@test';
|
||||||
|
const lines: string[] = ['BEGIN:VCALENDAR', 'VERSION:2.0'];
|
||||||
|
|
||||||
|
if (!overrides.allDay) {
|
||||||
|
lines.push('BEGIN:VEVENT');
|
||||||
|
lines.push(`UID:${uid}`);
|
||||||
|
lines.push('SUMMARY:Test event');
|
||||||
|
lines.push('DTSTART:20260615T140000Z');
|
||||||
|
lines.push('DTEND:20260615T150000Z');
|
||||||
|
} else {
|
||||||
|
lines.push('BEGIN:VEVENT');
|
||||||
|
lines.push(`UID:${uid}`);
|
||||||
|
lines.push('SUMMARY:All-day test');
|
||||||
|
lines.push('DTSTART;VALUE=DATE:20260615');
|
||||||
|
lines.push('DTEND;VALUE=DATE:20260616');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (overrides.rrule) {
|
||||||
|
lines.push(`RRULE:${overrides.rrule}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (overrides.reminderMinutes === 'absolute') {
|
||||||
|
lines.push('BEGIN:VALARM');
|
||||||
|
lines.push('ACTION:DISPLAY');
|
||||||
|
lines.push('DESCRIPTION:Reminder');
|
||||||
|
lines.push('TRIGGER;VALUE=DATE-TIME:20260615T120000Z');
|
||||||
|
lines.push('END:VALARM');
|
||||||
|
} else if (overrides.reminderMinutes !== 'none' && overrides.reminderMinutes !== undefined) {
|
||||||
|
lines.push('BEGIN:VALARM');
|
||||||
|
lines.push('ACTION:DISPLAY');
|
||||||
|
lines.push('DESCRIPTION:Reminder');
|
||||||
|
lines.push(`TRIGGER:-PT${overrides.reminderMinutes}M`);
|
||||||
|
lines.push('END:VALARM');
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.push('END:VEVENT');
|
||||||
|
lines.push('END:VCALENDAR');
|
||||||
|
return lines.join('\r\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
const WINDOW_START = new Date('2026-06-01T00:00:00Z');
|
||||||
|
const WINDOW_END = new Date('2026-07-01T00:00:00Z');
|
||||||
|
|
||||||
|
// Non-recurring event with reminderLeadMinutes=30 → occurrence carries 30
|
||||||
|
it('non-recurring event: occurrence carries reminderLeadMinutes from master (30 minutes)', () => {
|
||||||
|
const raw = makeVevent({ reminderMinutes: 30 });
|
||||||
|
|
||||||
|
const occurrences = expandOccurrences(
|
||||||
|
raw,
|
||||||
|
WINDOW_START,
|
||||||
|
WINDOW_END,
|
||||||
|
1,
|
||||||
|
'My Calendar',
|
||||||
|
1,
|
||||||
|
null,
|
||||||
|
'#4A90D9',
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(occurrences).toHaveLength(1);
|
||||||
|
expect(occurrences[0].reminderLeadMinutes).toBe(30);
|
||||||
|
});
|
||||||
|
|
||||||
|
// NULL-vs-0: master with 0-minute all-day lead → occurrence carries 0, not null
|
||||||
|
it('non-recurring all-day event: occurrence carries reminderLeadMinutes=0 (same-day, D-06 NULL-vs-0)', () => {
|
||||||
|
// Use a 0-minute trigger (same-day all-day)
|
||||||
|
const raw = makeVevent({ allDay: true, reminderMinutes: 0 });
|
||||||
|
|
||||||
|
const occurrences = expandOccurrences(
|
||||||
|
raw,
|
||||||
|
new Date('2026-06-01T00:00:00Z'),
|
||||||
|
new Date('2026-07-01T00:00:00Z'),
|
||||||
|
1,
|
||||||
|
'My Calendar',
|
||||||
|
1,
|
||||||
|
null,
|
||||||
|
'#4A90D9',
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(occurrences).toHaveLength(1);
|
||||||
|
// 0-minute all-day trigger → preset 0 → reminderLeadMinutes=0 (not null)
|
||||||
|
expect(occurrences[0].reminderLeadMinutes).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
// NULL: no VALARM in master → occurrence carries null
|
||||||
|
it('non-recurring event with no VALARM: occurrence carries reminderLeadMinutes=null', () => {
|
||||||
|
const raw = makeVevent({ reminderMinutes: 'none' });
|
||||||
|
|
||||||
|
const occurrences = expandOccurrences(
|
||||||
|
raw,
|
||||||
|
WINDOW_START,
|
||||||
|
WINDOW_END,
|
||||||
|
1,
|
||||||
|
'My Calendar',
|
||||||
|
1,
|
||||||
|
null,
|
||||||
|
'#4A90D9',
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(occurrences).toHaveLength(1);
|
||||||
|
expect(occurrences[0].reminderLeadMinutes).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
// D-10 series-level: recurring event with reminderLeadMinutes=60 → all occurrences carry 60
|
||||||
|
it('D-10 series-level: all recurring occurrences inherit the master reminderLeadMinutes=60', () => {
|
||||||
|
const raw = makeVevent({ reminderMinutes: 60, rrule: 'FREQ=WEEKLY;COUNT=3' });
|
||||||
|
|
||||||
|
const occurrences = expandOccurrences(
|
||||||
|
raw,
|
||||||
|
WINDOW_START,
|
||||||
|
WINDOW_END,
|
||||||
|
1,
|
||||||
|
'My Calendar',
|
||||||
|
1,
|
||||||
|
null,
|
||||||
|
'#4A90D9',
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(occurrences.length).toBeGreaterThan(0);
|
||||||
|
for (const occ of occurrences) {
|
||||||
|
expect(occ.reminderLeadMinutes).toBe(60);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user